vue-router
是什么
是vue官方的一個插件
專門用來實現一個SPA應用
基於vue的項目基本都會用到此庫
vuex vue-router 這兩個插件應用很廣泛
單頁Web應用(single page web application,SPA)
整個應用只有一個完整的頁面(這個完整的頁面,由多個組件組成)
點擊頁面中的鏈接不會刷新頁面, 本身也不會向服務器發請求
當點擊路由鏈接時, 只會做頁面的局部更新(組件切換)
數據都需要通過ajax請求獲取, 並在前端異步展現
路由
是一個key:value的映射關系
前台路由 路徑 和 要顯示的組件
{
path:'/home',
component:Home
}
當點擊鏈接的時候,路徑會發生變化,但是不會向服務器發請求,而是去顯示對應的組件
后台路由 路徑 和 匹配的函數
app.get('/users/info',function(){})
當點擊鏈接的時候,路徑會發生變化,而且會向服務器發請求,然后匹配到后端的一個函數處理這個路由的請求,返回需要的
數據
簡單理解前台路由:路由可以讓我們實現組件的切換和跳轉:
點擊鏈接,匹配路由,顯示對應的組件
路由組件和非路由組件
//宏觀上去看 如果一個組件是點擊鏈接之后才會出現的,那么就一定和路由相關,就被路由組件
//一個組件是在路由器中的路由中注冊的,就是路由組件
//一個組件不是在路由中注冊的就叫非路由組件
案例實現
1.安裝router, npm install vue-router
2.新建文件夾router,文件,新建index.js, 引入vue, router,
import Vue from "vue";
import Vuerouter from "vue-router";
//使用插件
Vue.use(Vuerouter);
暴露路由
// 暴露路由
export default Vuerouter({});
3.在入口文件main.js中,引入路由,注冊路由
import router from '@/router'
Vue.config.productionTip = false
new Vue({
el:'#root',
render: h => h(App),
router
//注冊路由器,注冊之后
//每個路由組件內部都可以通過this.$router拿到路由器對象
//每個路由組件內部都可以通過this.$route拿到當前的路由對象
})
4,在router中引入路由組件,並且配置路由對象
// 引入路由組件
import About from '@/pages/About'
import Home from '@/pages/Home'
// 暴露路由
export default new Vuerouter({
routes:[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
},
{ //重定向 path:'/', redirect:'/home'
}
]
});
在父組件中設置路由連接
<ul class="nav nav-stacked col-md-6" role="tablist"> <li role="presentation" class="active"> <router-link to="/home" aria-controls="home" role="tab" data-toggle="tab">Home</router-link> <!-- <a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a> --> </li> <li role="presentation"> <router-link to="/about" aria-controls="About" role="tab" data-toggle="tab">About</router-link> <!-- <a href="#About" aria-controls="About" role="tab" data-toggle="tab">About</a> --> </li> </ul>
<div class="tab-content col-md-6"> <keep-alive include="Home"> <router-view></router-view> </keep-alive> </div>
5.給點擊的路由連接設置樣式,.router-link-active點擊a標簽,會自動有這個類
.router-link-active{
color: red !important;
}
6.設置二級路由
在router的文件中,引入組件,配置二級路由對象
import Message from '@/pages/Message'
import News from '@/pages/News'
// 暴露路由
export default new Vuerouter({
routes:[
{
path:'/home',
component:Home,
children:[
{
// path:'/home/message',子路由組件,可以省略
path:'message', //簡寫
component:Message
},
{
path:'news', //簡寫
component:News
},
{
path:'', //默認代表/home路徑,重定向 redirect:'message' //可以簡寫成路徑,默認跳轉到message路徑
}
]
},
7.設置三級路由
export default new Vuerouter({
routes: [
{
path: "/home",
component: Home,
children: [
{
// path:'/home/message',子路由組件,可以省略
path: "message", //簡寫
component: Message, children: [
{
path: "msgdtil",
component: MsgDtil,
},
],
},
設置路由連接
<div role="tabpanel" class="tab-pane active" id="Message"> <ul class="list-group"> <li class="list-group-item" v-for="(message, index) in messages" :key="message.id"> <router-link to="/home/message/msgdtil" >{{message.content}}</router-link> <!-- <a href="##">message01</a> --> </li> </ul> <div class="alert alert-success" role="alert"> <!-- 設置路由組件 --> <router-view></router-view> </div> </div>
八,原始路由傳參,params和query參數
最原始的傳參
參數:params參數和路徑一樣 /message/10
query參數路徑后使用?去拼接起來的 /xxx/ ? aa = bb && xx = yy
<router-link :to="`/home/message/msgdetail/${m.id}?msgcontent=${m.content}`">{{m.content}}</router-link>
此時params參數,需要在改路由對象中設置鍵
{
path: "msgdtil/:mid",
component: MsgDtil,
},
路由組件中接收參數
<li class="list-group-item">messageID:{{$route.params.mid}}</li> <li class="list-group-item">message:{{$route.query.msgcontent}}</li>
九, 路由傳參,props簡化傳遞
如果路由組件不需要這么麻煩接收參數{{$route.params.mid}}, 那么可以通過在路由對象中配置props來簡化接收參數
使用props簡化路由傳參給子組件操作(路由當中傳參的三種操作)
1)布爾值
路由當中需要配置 props:true,只能接收params參數,它會把路由當中接收的參數,置為子組件的屬性
2)對象
很少用,只能給子組件傳遞默認靜態值
3)函數
用的比較多,比較靈活,可以把params和query的參數都映射為子組件的屬性
props(route){ //route就是當前我這個路由對象
//把路由對象當中的參數,不管什么參數
//全部拿到作為子組件的屬性去使用
return {
msgId:route.params.msgId,
msgContent:route.query.msgContent
}
}
在路由對象中配置props
{
path:'msgdetail/:mid',
component:MsgDetail,
// props:true //代表只是針對params參數,屬性傳遞給相應的路由組件對象
// props:{username:'趙麗穎'} //props可以是一個對象,對象內部的數據是要傳遞給路由組件的靜態數據
// props(route){
// //把路徑傳參傳遞過來的params參數和query參數,都取出來,自己封裝為一個對象
// //好讓props可以進行屬性傳遞
// return {mid:route.params.mid,msgcontent:route.query.msgcontent}
// },
//箭頭函數寫法
props: route => ({mid:route.params.mid,msgcontent:route.query.msgcontent}),
name:'msgdetail'
}
export default {
name: "MsgDetail",
props: ["mid", "msgcontent"],
data() {
return {
title: ""
};
},
<!-- <li class="list-group-item">messageID:{{$route.params.mid}}</li> --> <li class="list-group-item">messageID:{{mid}}</li> <!-- <li class="list-group-item">message:{{$route.query.msgcontent}}</li> --> <li class="list-group-item">message:{{msgcontent}}</li>
十,命名路由寫法,對象形式
路由鏈接組件中給路由傳參可以寫成對象形式,前提需要給路由起名字name,也叫命名路由
在路由對象中命名該路由
{
path:'msgdetail/:mid',
component:MsgDetail,
// props:true //代表只是針對params參數,屬性傳遞給相應的路由組件對象
// props:{username:'趙麗穎'} //props可以是一個對象,對象內部的數據是要傳遞給路由組件的靜態數據
// props(route){
// //把路徑傳參傳遞過來的params參數和query參數,都取出來,自己封裝為一個對象
// //好讓props可以進行屬性傳遞
// return {mid:route.params.mid,msgcontent:route.query.msgcontent}
// },
props: route => ({mid:route.params.mid,msgcontent:route.query.msgcontent}),
name:'msgdetail'
}
路由連接命名寫法
<!-- 原始字符串路徑寫法 --> <!-- <router-link :to="`/home/message/msgdetail/${m.id}?msgcontent=${m.content}`">{{m.content}}</router-link> --> <!-- 對象寫法 --> <router-link :to="{name:'msgdetail',params:{mid:m.id},query:{msgcontent:m.content}}">{{m.content}}</router-link>
<template> <ul class="list-group"> <!-- <li class="list-group-item">messageID:{{$route.params.mid}}</li> --> <li class="list-group-item">messageID:{{mid}}</li> <!-- <li class="list-group-item">message:{{$route.query.msgcontent}}</li> --> <li class="list-group-item">message:{{msgcontent}}</li> <li class="list-group-item">{{title}}</li> </ul> </template> <script> const titleArr = [ { id: 1, title: "msg01" }, { id: 2, title: "msg02" }, { id: 3, title: "msg03" } ]; export default { //關於路由組件銷毀的問題,因為多個路由連接,跳轉的都是同一個組件, //路由傳參的id和組件自定義的id去匹配,獲取title,填充模板數據 name:'MsgDtil', //路由傳參,props傳遞 props:['mid', 'msgcontent'], data() { return { title:'' }; }, mounted(){ //路由傳參過來的數字,自動變成了字符竄形式 console.log(typeof(this.mid)) // string this.title= titleArr.find(item =>item.id === this.mid*1).title } }; </script> <style scoped ></style>
效果圖
數據一直沒有變化,因為多個路由連接都是跳轉同一個路由組件,路由組件的生命周期一直存在,沒有銷毀,所以mounted加載后,新數據不會渲染
此時需要解決該bug,因為路由對象的params和query參數是不斷變化的,此時我們需要監視路由對象$route, 只要路由對象的參數發生變化,就可以重新更新數據
<template> <ul class="list-group"> <!-- <li class="list-group-item">messageID:{{$route.params.mid}}</li> --> <li class="list-group-item">messageID:{{mid}}</li> <!-- <li class="list-group-item">message:{{$route.query.msgcontent}}</li> --> <li class="list-group-item">message:{{msgcontent}}</li> <li class="list-group-item">{{title}}</li> </ul> </template> <script> const titleArr = [ { id: 1, title: "msg01" }, { id: 2, title: "msg02" }, { id: 3, title: "msg03" } ]; export default { //關於路由組件銷毀的問題,因為多個路由連接,跳轉的都是同一個組件, //路由傳參的id和組件自定義的id去匹配,獲取title,填充模板數據 name:'MsgDtil', //路由傳參,props傳遞 props:['mid', 'msgcontent'], data() { return { title:'' }; }, mounted(){ //路由傳參過來的數字,自動變成了字符竄形式 console.log(typeof(this.mid)) // string this.changeTitle() }, //監視路由變化 watch:{ $route(newRoute, oldRoute){ this.changeTitle() } }, methods:{ changeTitle(){ this.title= titleArr.find(item =>item.id ===this.mid*1 ).title } } }; </script> <style scoped ></style>
路由組件和非路由組件的最大區別
路由組件的生命周期是點擊鏈接的時候,才開始的,路由組件才會創建,mounted才能執行
路由組件在切換的時候,會被銷毀,顯示的時候重新創建
同一個路由組件傳參顯示不同數據,mounted回調只會執行一次,因為是一個組件
十一,緩存路由組件
使用的是vue的一個組件,參考vue的官方文檔 使用這個東西可以保證我們在切換組件的時候,原來顯示的組件不被銷毀。數據不會銷毀 <keep-alive include="Home"> Home是對應的組件對象的名字,不是路由的名字 <router-view></router-view> </keep-alive>
十二,編程式路由導航
定義一個事件,函數
<ul class="list-group"> <li class="list-group-item" v-for="(n, index) in news" :key="n.id"> {{n.content}} <button type="button" class="btn btn-primary" data-toggle="button" aria-pressed="false" autocomplete="off" @click="changeLr(n)" > {{ n.id }}</button> </li> </ul>
methods: {
// handlerBack(){
// this.$router.back()
// },
toNewsDetail(n) {
//編程式導航(路由)
// this.$router.push(`/home/news/newsdetail/${n.id}?newscontent=${n.content}`)//就是實現路由鏈接的效果
//對象形式
const location = {
name: "newsdetail",
params: { nid: n.id },
query: { newscontent: n.content },
};
// this.$router.push(location); //就是實現路由鏈接的效果
this.$router.replace(location);
},
},
1)this.$router.push(path): 相當於點擊路由鏈接(可以返回到當前路由界面)
2) this.$router.replace(path): 用新路由替換當前路由(不可以返回到當前路由界面)
3) this.$router.back(): 請求(返回)上一個記錄路由
$router.push()和$router.replace()的區別,返回有區別。
$router.push()是往歷史記錄里面追加
$router.replace()每一次都是覆蓋添加
<button @click="$router.back()">返回</button>
路由模式hash和history之間的區別
解決history路由404
hash模式:
路徑中帶#: http://localhost:8080/#/home/news
發請求的路徑: http://localhost:8080 項目根路徑,將域和路由路徑用#隔開,
響應: 返回的總是index頁面 ==> path部分(/home/news)被解析為前台路由路徑
history模式:
路徑中不帶#: http://localhost:8080/home/news
發請求的路徑: http://localhost:8080/home/news
響應: 404錯誤,被當做后台路由,去找資源,顯然8080端口是沒有該資源的
希望: 如果沒有對應的資源, 返回index頁面, path部分(/home/news)被解析為前台路由路徑
在路由對象中配置路由模式
export default new VueRouter({
mode:'history',
///home/message/msgdetail/1
routes:[
{
path:'/home',
component:Home,
解決history模式404的方式
解決: 添加配置
devServer添加: historyApiFallback: true, // 任意的 404 響應都被替代為 index.html
output添加: publicPath: '/', // 引入打包的文件時路徑以/開頭
在webpack配置文件配置
//3. 增加 devServer 配置
devServer: {
historyApiFallback: true, // 任意的 404 響應都被替代為 index.html
open: true, // 自動打開瀏覽器
compress: true, // 啟動gzip壓縮
port: 3000, // 端口號
quiet:true,
// 輸出配置
output: {
// 輸出文件名
filename: 'built.js',
//輸出文件路徑配置
path: path.resolve(__dirname, 'dist'),
//1. 添加 devServer 服務后需要調整輸出的路徑
publicPath: '/'
此時在index.html引入的css不能是相對路徑了,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- <link rel="stylesheet" href="./css/bootstrap.css"> --> <link rel="stylesheet" href="/css/bootstrap.css"> <style> .list-group-item { position: relative; } .btn { width: 80px; height: 30px; position: absolute; right: 0; bottom: 5px; } .router-link-active{ color: red !important; } </style> </head> <body> <div id="root"></div> </body> </html>