Vue Router 基礎
讓我們先來了解下Vue Router的簡單使用吧,先了解怎麼使用,之後再去想辦法怎麼去實現
1.簡介
路由:本質上是一種對應關係
分類分為前端路由和後端路由
後端路由
比如node.js 的路由是 URL的請求地址和服務器上面的資源對應,根據不同的請求地址返回不同的資源
前端路由
在SPA(單頁應用)中根據用戶所觸發的事件改變了URL 在無需刷新的前提下 顯示不同的頁面內容,比如等下就要講的Vue Router
2.Vue-Router最基礎的使用步驟
2.1.引入Vue-Router文件
<!-- 使用vue router前提 vue 必不可少 -->
<script src="./js/vue.js"></script>
<!-- 引入vue-router文件 -->
<script src="./js/vue-router_3.0.2.js"></script>
2.2.在頁面上添加 router-link 和 router-view
<!-- 添加路由 -->
<!-- 會被渲染為 <a href="#/home"></a> -->
<router-link to="/home">Home</router-link>
<router-link to="/login">Login</router-link>
<!-- 展示路由的內容 -->
<router-view></router-view>
2.3.創建路由組件
//創建路由組件
const home = {
template: `
<div>歡迎來到{{name}}</div>
`,
data() {
return {
name: '首頁',
}
},
}
const login = {
template: `
<div>歡迎來到登錄頁</div>
`,
}
2.4.配置路由規則
// 配置路由規則
const router = new VueRouter({
routes: [
//每一個路由規則都是一個對象
//path 路由的 hash地址
//component 路由的所展示的組件
{
path: '/',
// 當訪問 '/'的時候 路由重定向 到新的地址 '/home'
redirect: '/home',
},
{
path: '/home',
component: home,
},
{
path: '/login',
component: login,
},
],
})
2.5.掛載路由
let vm = new Vue({
el: '#app',
data: {},
methods: {},
// 掛載到vue 上面
router,
})

3.嵌套路由
這裡的嵌套路由是基於上面的例子繼續寫的
3.1.在路由裡面添加 子路由鏈接和 佔位符
//創建路由組件
const home = {
template: `
<div>
歡迎來到首頁
<br>
<!-- 子路由鏈接 -->
<router-link to="/tab1">Tab1</router-link>
<router-link to="/tab2">Tab2</router-link>
<!-- 子路由展示 -->
<router-view></router-view>
</div>
}
複製代碼
3.2.添加路由組件
// 創建兩個子路由組件
const tab1 = {
template: `
<div>
子路由1
</div>
`,
}
const tab2 = {
template: `
<div>
子路由2
</div>
`,
}
複製代碼
3.3.配置路由規則
// 配置路由規則
const router = new VueRouter({
routes: [
{
path: '/home',
component: home,
//children 表示子路由規則
children: [
{ path: '/tab1', component: tab1 },
{ path: '/tab2', component: tab2 },
],
},
],
})
複製代碼

4.動態路由
path屬性加上/:id 使用route對象的params.id獲取動態參數
比如現在有這麼多個路由,如果自己也配置多個路由,豈不是有點。。。多餘
<div id="app">
<!-- 添加路由 -->
<!-- 會被渲染為 <a href="#/home"></a> -->
<router-link to="/goods/1">goods1</router-link>
<router-link to="/goods/2">goods2</router-link>
<router-link to="/goods/3">goods3</router-link>
<router-link to="/goods/4">goods4</router-link>
<!-- 展示路由的內容 -->
<router-view></router-view>
</div>
然後這裡就可以使用 動態路由來解決
<script>
//創建路由組件
const goods = {
// this.$route.parms.id 可以省略 this
template: `
<div>歡迎來到商品 {{$route.params.id}}頁</div>
`,
}
// 配置路由規則
const router = new VueRouter({
routes: [
{
// 加上`/:id`
path: '/goods/:id',
component: goods,
},
],
})
let vm = new Vue({
el: '#app',
data: {},
methods: {},
// 掛載到vue 上面
router,
})
</script>

最後提一下還可以用query進行傳參.
// 比如
<router-link to="/goods?id=1">goods</router-link>
複製代碼
然後使用this.$route.query.id就可以在路由組件中獲取到id
添加動態路由
使用 this.$router.addRoutes([]) 可以添加動態路由,裡面傳遞是一個數組 和 routes裡面一樣
5.路由傳參
我們可以使用 props 進行傳值
為啥要用 props 進行傳值,route不香了嗎,確實route 不夠靈活
props 值有三種情況
5.1.布爾值類型
//創建路由組件
const goods = {
// 使用props接收
props: ['id'],
template: `
<div>歡迎來到商品 {{id}}頁</div>
`,
}
// 配置路由規則
const router = new VueRouter({
routes: [
{
path: '/goods/:id',
component: goods,
//props為true, route.params將會被設置為組件屬性
props: true,
},
],
})
複製代碼
5.2.對象類型
但是這裡就獲取不到 id 了,會報錯
這裡的id 需要 $route.params.id 獲取
const goods = {
// 使用props接收
props: ['name', 'info', 'id'],
// 這裡的 id 是獲取不到的
template: `
<div>{{info}}來到{{name}} {{id}}頁</div>
`,
}
// 配置路由規則
const router = new VueRouter({
routes: [
{
path: '/goods/:id',
component: goods,
//props為對象 就會把這個對象傳遞的路由組件
//路由組件使用props接收
props: {
name: '商品',
info: '歡迎',
},
},
],
})
複製代碼
5.3.函數
const goods = {
// 使用props接收
props: ['name', 'info', 'id'],
template: `
<div>{{info}}來到{{name}} {{id}}頁</div>
`,
}
// 配置路由規則
const router = new VueRouter({
routes: [
{
path: '/goods/:id',
component: goods,
//prop是一個函數的話 就可以組合傳值
props: (route) => {
return {
name: '商品',
info: '歡迎',
id: route.params.id,
}
},
},
],
})
複製代碼
6.route 和 router
在上面提到了route 那麼和 router有什麼區別呢
- route為當前router跳轉對象裡面可以獲取path,params,hash,query,fullPath,matched,name
- router為VueRouter實例用 new VueRouter創建的實例,想要導航到不同URL,則使用router.push方法
- routes是router路由實例用來配置路由對象(順帶提一下)
7.命名路由
路由組件
//創建路由組件
const goods = {
// 使用props接收
props: ['id'],
template: `
<div>商品{{id}}頁</div>
`,
}
複製代碼
路由配置
//配置路由
const router = new VueRouter({
routes: [
{
path: '/goods/:id',
// 命名路由
name: 'goods',
component: goods,
},
],
})
複製代碼
綁定 :to 通過name找到定義的路由 還可以使用 params 傳遞參數
<router-link :to="{name: 'goods', params: { id: 1 } }">goods1</router-link>
<!-- 展示路由的內容 -->
<router-view></router-view>
複製代碼
8.編程式導航
8.1.聲明式導航
既然提到了編程式導航,那麼先簡單說下聲明式導航
上面所展示的都是聲明是導航 比如router-link
<router-link to=”/goods/1″>goods1</router-link>
還有a標籤
<a href=”#/goods/1″>goods1</a>
8.2.編程式導航
使用javaScript來控制路由跳轉
在普通的網頁中使用 loaction.href window.open 等等進行跳轉
現在我要介紹的是Vue Router中的編程式導航
我們平時都是用router.push() **router.go(n)**方法進行跳轉
//字符串
this.$router.push('/home')
//對象
this.$ruter.push({path:'/home'})
//比如這個 /goods?id=1
this.$router.push({path:'/goods',query:{id:'1'}})
//命名路由 /goods/1
this.$router.push({name:'goods',params:{id:1}})
//後退
this.$router.go(-1)
複製代碼
9.路由守衛
9.1.全局守衛
router.beforeEach 全局守衛 對所有的路由都起作用
router.beforeEach((to, from, next) => {
next();//使用時,千萬不能漏寫next!!!
}).catch(()=>{
//跳轉失敗頁面
next({ path: '/error', replace: true, query: { back: false }}
)
})
複製代碼
全局的守衛的三個參數
to: 即將要進入的目標 路由對象
from: 當前導航正要離開 路由對象
next: 參數不同做的事也不同
next() 直接進入下一個鉤子
next(false) 停止當前導航
next(‘/路徑’) 跳轉到path路由地址 當然這裡面也可以寫成對象形式 next({path : ‘/路徑’}) next(error): 如果傳入參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError()
9.2.路由獨享的守衛
beforeEnter 路由對象獨享的守衛寫在routes裡面
const router = new VueRouter({
routes: [
{
path: '/goods',
component: goods,
beforeEnter: (to, from, next) => {
// 一樣的用法
}
}
]
})
複製代碼
9.3.組件內的守衛(了解)
組件內的守衛 寫在組件內部 下面是官方介紹
- beforeRouteEnter 進入路由前,組件還沒有被實例化所以這裡無法獲取到this
- beforeRouteUpdate (2.2) 這個階段可以獲取this,在路由復用同一個組件時觸發
- beforeRouteLeave 這個階段可以獲取this,當離開組件對應的路由時,此時可以用來保存數據,或數據初始化,或關閉定時器等等
const goods = {
template: `<div>goods</div>`,
beforeRouteEnter (to, from, next) {
// 具體邏輯
},
beforeRouteUpdate (to, from, next) {
// 具體邏輯
},
beforeRouteLeave (to, from, next) {
// 具體邏輯
}
}
複製代碼
10.組件緩存keep-alive
頁面重新加載會重新渲染頁面比如回退的時候等等,我們有的組件它不是一個活動的(數據不變)不希望它被重新渲染,所以這裡就可以使用 <keep-alive> </keep-alive> 包裹起來,這樣就不會觸發created鉤子
應用場景:獲取一個商品的詳情然後回退在前進的時候就使用緩存,提高性能
10.1.不使用 keep-alive例子
這裡home 組件在created進行打印當前的時間
<div id="app">
<router-link to="/home">home</router-link>
<router-link to="/login">login</router-link>
<router-view></router-view>
</div>
複製代碼
<script>
const login = {
template: `
<div>Login</div>
`,
}
const home = {
template: `
<div>Home</div>
`,
created() {
console.log(new Date())
},
}
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
component: home,
},
{
path: '/login',
component: login,
},
],
})
let vm = new Vue({
el: '#app',
data: {},
methods: {},
router,
})
</script>
複製代碼

如上,每切換home 的路由 組件就會重新渲染,打印當前的時間
如果使用 keep-alive 會有什麼效果呢
10.2.使用keep-alive
這裡只需簡單的包裹起來就行了
<div id="app">
<router-link to="/home">home</router-link>
<router-link to="/login">login</router-link>
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
複製代碼

可以看到的是只打印一次,說明切換了路由它並沒有重新渲染組件
當然可以在 組件內取個name名字 keep-alive 標籤裡面添加 include 屬性就可以對相應的組件進行緩存
const login = {
name: login,
template: `
<div>Login</div>
`,
}
const home = {
name: home,
template: `
<div>Home</div>
`,
created() {
console.log(new Date())
},
}
複製代碼
<div id="app">
<router-link to="/home">home</router-link>
<router-link to="/login">login</router-link>
<keep-alive include="login,home">
<router-view></router-view>
</keep-alive>
</div>
複製代碼
10.3.activated 和 deactivated
keep-alive 生命周期執行順序
第一次訪問路由時:
- created–>mounted –>activated
- deactivated在退出後觸發
以後進入只會觸發 activated
11.hash 和 history 模式
11.1.hash模式
在vue-router中默認使用的是 hash 模式
hash是url中的錨點就是**#,通過錨點作為路由地址,我們通常改變的是改變#**後面部分,實現瀏覽器渲染指定的組件.,錨點發生改變會觸發 onhashchange 事件
11.2.history模式
history 模式就是平時正常的地址,使用方面需要服務器支持
如果訪問的路徑資源沒有 直接就是 404
在HTML5後新增了兩個API
pushState(): IE10後支持
replaceState()
在vue-router中如果要使用 history 模式需要指定
const router = new VueRouter({
mode: 'history'
})
複製代碼
實現一個基礎 Vue Router
複習上面的路由的基礎那麼我們不如來寫個Vue Router吧
實現的這個 Vue Router是基於 history模式
所有的步驟都放到代碼的注釋中,每一行都寫個注釋
這個簡單的沒有按照Vue Router源碼來寫主要是一些基礎功能的實現
為後面的按照源碼寫打基礎
1.註冊全局Vue Router
首先就是先註冊自己的 Vue Router
判斷是否註冊了組件
在Vue實例創建完成進行註冊
// 保存一個全局變量 Vue
let _Vue = null
// 默認導出自己寫的 VueRouter
export default class MyVueRouter {
// 實現install 註冊 MyVueRouter vue提供install可供我們開發新的插件及全局註冊組件等
// 把Vue傳進去
static install(Vue) {
// 定義一個標識判斷是否註冊了 MyVueRouter ,註冊了就不用下一步了
if (MyVueRouter.install.installed) return
// 沒有就進行下面的,把標識改變true
MyVueRouter.install.installed = true
// 把全局變量 _Vue 保存
_Vue = Vue
// 為了獲取Vue中的this執行這裡使用 混入
_Vue.mixin({
// 在Vue實例創建好的時候進行操做
beforeCreate() {
// 判斷是否是實例創建還是組件創建 ,可以判斷是否掛載 了router
if (this.$options.router) {
// 把router註冊到 _Vue上
_Vue.prototype.$router = this.$options.router
}
},
})
}
}
複製代碼
2.實現 構造方法
optoins 保存傳入的規則
routerMap 確定地址和組件的關係
current 表示當前的地址是響應式的之後渲染組件和它相關
export default class MyVueRouter {
...
//實現構造
constructor(optoins) {
// 這個保存的是 routes
this.optoins = optoins
// routerMap 保存路由和 組件之間的關係
this.routerMap = {}
// 用來記錄數據 這裡面的數據都是 響應式
this.data = _Vue.observable({
// 當前路由的地址
current: '/',
})
}
}
複製代碼
3.解析路由規則
傳入的路由規則拿到一個對象里 地址 和 組件一一匹配
export default class MyVueRouter {
...
// 解析路由規則
createRouterMap() {
// 把之前構造函數的中的傳入的 routes 規則進行遍歷
this.optoins.routes.forEach((item) => {
// 把路由 和 組件的對應關係添加到 routerMap中
this.routerMap[item.path] = item.component
})
}
}
複製代碼
4.實現 router-link 組件
router-link就是頁面上所展示的路由鏈接
因為一般使用的基本都是運行版的Vue 所以自己把組件轉為 虛擬DOM
還有就是鏈接會刷新的問題
自己寫個函數進行跳轉阻止默認事件
還得注意對應的路由所要渲染的組件
export default class MyVueRouter {
...
// 實現組件
initComponents(Vue) {
// 實現 router-link組件
Vue.component('router-link', {
props: {
// router-link上面的to屬性將訪問的地址
to: String,
},
// 由於運行版的Vue不能渲染template所以這裡重新寫個render 這裡h 也是個函數
// template: `<a :href="to"><slot></slot></a>`,
render(h) {
// 第一個參數是標籤
return h(
'a',
// 第二個參數是對象是 tag 裡面的屬性
{
// 設置屬性
attrs: {
href: this.to,
},
// 綁定事件
on: {
// 重新複寫點擊事件,不寫的話會點擊會向服務器發送請求刷新頁面
click: this.myClick,
},
},
// 這個是標籤裡面的內容 這裡渲染是 默認插槽
[this.$slots.default]
)
},
methods: {
//router-link的點擊事件
myClick(e) {
// 因為我這裡是模擬是 history的路由所以用pushState ,hash路由可以這裡用 push
// 使用history修改瀏覽器上面的地址
// pushState 第一個參數是傳遞的參數,第二個是標題,第三個是鏈接
history.pushState({}, '', this.to)
// 渲染相應的組件
// 渲染的頁面也需要改變 data中的current是響應式的 router-view是根據current來渲染的
this.$router.data.current = this.to
// 阻止默認跳轉事件
e.preventDefault()
},
},
})
複製代碼
5.實現 router-view 組件
這裡從之前解析的規則裡面拿到當前的對應的組件進行轉為虛擬DOM
最後router-view佔位渲染到頁面上
export default class MyVueRouter {
...
// 實現組件
initComponents(Vue) {
// 實現 router-view組件
Vue.component('router-view', {
render(h) {
// 獲取的當前路徑所對應的組件
// 因為當前this是Vue,this.$router才是MyVueRouter
const component = this.$router.routerMap[this.$router.data.current]
// 轉化為虛擬Dom
return h(component)
},
})
}
}
複製代碼
6.前進和後退
在完成之前的編寫還是不夠的,因為在瀏覽器點後退和前進雖然改變了瀏覽器的地址,但是組件卻沒有刷新,下面就來解決這個問題
export default class MyVueRouter {
...
// 初始化事件
initEvent() {
// 監聽瀏覽器地址的改變
window.addEventListener('popstate', () => {
// 改變VueRouter的當前的地址 重新渲染組件
this.data.current = window.location.pathname
})
}
}
複製代碼
7.在router掛載後進行初始化
最後寫個函數進行初始化
在router註冊到Vue之後進行 初始化
export default class MyVueRouter {
// 初始化
init() {
// 解析路由規則
this.createRouterMap()
// 初始化組件
this.initComponents(_Vue)
// 初始化事件
this.initEvent()
}
static install(Vue) {
if (MyVueRouter.install.installed) return
MyVueRouter.install.installed = true
_Vue = Vue
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
// 註冊完router後進行初始化
this.$options.router.init()
}
},
})
}
...
}
複製代碼
8.放上完整的 index.js
// 保存一個全局變量 Vue
let _Vue = null
export default class MyVueRouter {
// 實現install 註冊 MyVueRouter vue提供install可供我們開發新的插件及全局註冊組件等
// 把Vue傳進去
static install(Vue) {
// 定義一個標識判斷是否註冊了 MyVueRouter ,註冊了就不用下一步了
if (MyVueRouter.install.installed) return
// 沒有就進行下面的,把標識改變true
MyVueRouter.install.installed = true
// 把全局變量 _Vue 保存
_Vue = Vue
// 為了獲取Vue中的this執行這裡使用 混入
_Vue.mixin({
// 在Vue實例創建好的時候進行操做
beforeCreate() {
// 判斷是否是實例創建還是組件創建 ,可以判斷是否掛載 了router
if (this.$options.router) {
// 把router註冊到 _Vue上
_Vue.prototype.$router = this.$options.router
// 註冊完router後進行初始化
this.$options.router.init()
}
},
})
// 判斷是否掛載
}
// 實現構造方法
constructor(optoins) {
// 這個保存的是 routes
this.optoins = optoins
// routerMap 保存路由和 組件之間的關係
this.routerMap = {}
// 用來記錄數據 這裡面的數據都是 響應式
this.data = _Vue.observable({
// 當前路由的地址
current: '/',
})
}
// 解析路由規則
createRouterMap() {
// 把之前構造函數的中的傳入的 routes 規則進行遍歷
this.optoins.routes.forEach((item) => {
// routes中的每一項都是一個對象 { path: '/XXX', component: XXX}
// 把路由 和 組件的對應關係添加到 routerMap中
this.routerMap[item.path] = item.component
})
}
// 實現組件
initComponents(Vue) {
// 實現 router-link組件
Vue.component('router-link', {
props: {
// router-link上面的to屬性將訪問的地址
to: String,
},
// 由於運行版的Vue不能渲染template所以這裡重新寫個render 這裡h 也是個函數
// template: `<a :href="to"><slot></slot></a>`,
render(h) {
// 第一個參數是標籤
return h(
'a',
// 第二個參數是對象是 tag 裡面的屬性
{
// 設置屬性
attrs: {
href: this.to,
},
// 綁定事件
on: {
// 重新複寫點擊事件,不寫的話會點擊會向服務器發送請求刷新頁面
click: this.myClick,
},
},
// 這個是標籤裡面的內容 這裡渲染是 默認插槽
// 比如<router-link to="/">首頁</router-link>
// 插槽就是給首頁兩個字留位置,當前這只是個例子
[this.$slots.default]
)
},
methods: {
//router-link的點擊事件
myClick(e) {
// 因為我這裡是模擬是 history的路由所以用pushState ,hash路由可以這裡用 push
// 使用history修改瀏覽器上面的地址
// pushState 第一個參數是傳遞的參數,第二個是標題,第三個是鏈接
history.pushState({}, '', this.to)
// 渲染相應的組件
// 渲染的頁面也需要改變 data中的current是響應式的 router-view是根據current來渲染的
this.$router.data.current = this.to
// 阻止默認跳轉事件
e.preventDefault()
},
},
})
// 實現 router-view組件
Vue.component('router-view', {
render(h) {
// 獲取的當前路徑所對應的組件
// 因為當前this是Vue,this.$router才是MyVueRouter
const component = this.$router.routerMap[this.$router.data.current]
// 轉化為虛擬Dom
return h(component)
},
})
}
// 初始化事件
initEvent() {
// 監聽瀏覽器地址的改變
window.addEventListener('popstate', () => {
// 改變VueRouter的當前的地址 重新渲染組件
this.data.current = window.location.pathname
})
}
// 初始化
init() {
// 解析路由規則
this.createRouterMap()
// 初始化組件
this.initComponents(_Vue)
// 初始化事件
this.initEvent()
}
}
複製代碼
到了這裡基礎的實現功能差不多了,上面的例子是為了下面打基礎,所有的功能實現基本都是在一個文件下很不嚴謹,下面就嚴格按照Vue Router 源碼來實現自己 Vue Router
Vue Router實現
經過上面簡單的實現,現在我們按照Vue Router源碼的方式進行編寫
1.首先是Vue Router 構造
/* index.js */
// 導出自己寫的 VueRouter
export default class VueRouter {
// 實現構造函數功能
constructor(options) {
// 獲取options中的routes路由規則 沒有就為空數組
this._options = options.routes || []
}
// 初始化
init(Vue) {}
}
複製代碼
2.註冊組件 install
在 install.js 對自己寫的Vue-Router進行全局的註冊
之後還會在這裡創建 router∗∗∗∗router** **router∗∗∗∗route
還有註冊 router-link router-view
/* install.js */
// 定義一個全局 的Vue
export let _Vue = null
// 導出 install方法
export default function install(Vue) {
// 保存到全局的Vue
_Vue = Vue
// 混入
_Vue.mixin({
// Vue實例創建完畢之後操做
beforeCreate() {
// 這裡是new Vue
if (this.$options.router) {
// 保存 Vue
this._routerRoot = this
// 保存 Vue Router 的實例,以後可以通過Vue Router構造的一些方法
this._router = this.$options.router
// 調用Vue Router的init(Vue) 初始化操做
this._router.init(this)
} else {
// 這裡是創建 Vue的組件等等
// 判斷是否有父組件 ,有的話就把父組件的 _roterRoot(也就是Vue)給 子組件
// 沒有父組件就把 this 這是也是(Vue) 給子組件
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
},
})
}
複製代碼
然後在 index.js中導入install 進行為構造添加 install
// 導入 install
import install from './install'
// 導出自己寫的 VueRouter
export default class VueRouter {
...
}
// 為VueRouter 添加 install方法
VueRouter.install = install
複製代碼
3.編寫 create-route-map.js
這個主要的作用就是用來解析傳遞過來的路由 需要導出然後在 create-matcher.js進行使用
具體的細節都寫了注釋
/* create-route-map.js */
// 導出具體的路由解析
/**
*
* @param {*} routes 路由規則
* @param {*} oldPathList 路由列表
* @param {*} oldPathMap 路由和組件的對應關係
*/
export default function createRouteMap(routes, oldPathList, oldPathMap) {
// 傳入了就是添加動態路由 沒有傳入就默認為空
const pathList = oldPathList || []
const pathMap = oldPathMap || []
// 遍歷規則操作
routes.forEach((route) => {
// 記錄路由 也是核心的解析路由 為了分工明確寫的外面
addRouteRecord(route, pathList, pathMap)
})
// 返回新的路由列表 和 路由對應關係
return {
pathList,
pathMap,
}
}
/**
*
* @param {*} route 路由規則
* @param {*} pathList 路由列表
* @param {*} pathMap 路由和組件之間的對應關係
* @param {*} parentRecord 父路由
*/
function addRouteRecord(route, pathList, pathMap, parentRecord) {
// 路由地址 判斷是否存在父級的路由 有的話拼接父級路由和當前路由的path 沒有就是當前route.path
const path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path
// record作為一個路由記錄 記錄了路由地址,組件,父級路由 用於路由對應關係去對應相對應的path
const record = {
path,
component: route.component,
parent: parentRecord,
}
// 判斷是否在路由列表中 存在當前路由,不存在進行添加當前路由,更新路由列表
if (!pathList[path]) {
// 向路由列表中添加路由
pathList.push(path)
// 向路由對應關係中 添加path 相對應的記錄
pathMap[path] = record
}
// 判斷當前的 路由是否有子路由,有的話進行遞歸
if (route.children) {
route.children.forEach((childRoute) => {
// 就簡單說下最後一個參數 就是父級路由記錄
addRouteRecord(childRoute, pathList, pathMap, record)
})
}
}
複製代碼
4.編寫 create-matcher.js
這個模塊的意義也是解析路由不過這個是個指揮家,上面實現的是具體解析操作
在這個模塊里進行調用上面的具體解析路由的方法就行了
有了上面面具體的路由解析,這個create-matcher.js就容易實現了,只需要簡單的調用它即可
這個模塊返回了兩個方法
match : 根據路由路徑創建路由規則對象,之後就可以通過 規則對象獲取到所有的路由信息然後拿到所有的組件進行創建
addRoutes : 添加動態路由
/* create-matcher.js */
// 導入具體的路由解析規則
import createRouteMap from './create-route-map'
// 導出解析路由規則 傳入的是規則
export default function createMatcher(router) {
// pathList 路由的列表 pathMap 路由與組件的對應關係 nameMap這裡沒有考慮,先完成個簡單的
// 具體的解析規則是使用 createRouteMap
const { pathList, pathMap } = createRouteMap(router)
// match是 從pathMap 根據path獲取 相應的路由記錄
function match(path) {
//待實現
}
// 添加動態路由
function addRoutes(router) {
// 添加動態路由肯定也要解析路由規則
createRouteMap(router, pathList, pathMap)
}
// 返回match 和 addRoutes
return {
match,
addRoutes,
}
}
複製代碼
然後在index.js也就是Vue Router的構造中使用 createMatcher. 使用this.matcher接收
// 導入 install
import install from './install'
// 導入解析路由
import createMatcher from './create-matcher'
// 導出自己寫的 VueRouter
export default class VueRouter {
// 實現構造函數功能
constructor(options) {
// 獲取options中的routes路由規則 沒有就為空數組
this._routes = options.routes || []
// 解析路由 傳入規則 這裡還返回了兩個方法 match,addRoutes 用matcher接收一下之後有用
this.matcher = createMatcher(this._routes)
}
// 初始化
init(Vue) {}
}
// 為VueRouter 添加 install方法
VueRouter.install = install
複製代碼
5.編寫 createMatcher
看見上面在 createMatcher中定義了 一個match了嗎,
match是 從pathMap 根據path獲取 相應的路由記錄
上面還沒有去實現,現在來實現它
需要實現它的話還需要編寫個 createRoute 方法,我這裡寫在 uitl/route.js模塊里
/* util/route.js */
// 導出 createRoute
/**
*
* @param {*} record 傳過來的記錄
* @param {*} path 路由地址
* @returns
*/
export default function createRoute(record, path) {
// 保存路由的記錄 裡面可能有多個路由 是這種模式保存 [parentRecord,childRecord]
const matched = []
// 判斷是否是子路由
// 下面 record = record.parent 在不斷向上找parent有繼續執行
// 沒有就直接return 下面的對象
while (record) {
// 循環得到的 record不斷插入到 數組的最前面
matched.unshift(record)
// 把父記錄給當前record 繼續循環
record = record.parent
}
// 返回path 和 matched 以便之後 router-view渲染
return {
path,
matched,
}
}
複製代碼
上面編寫了 createRoute方法我們就可以在 create-mathcer.js 調用 來獲取到記錄了
然後再 create-mathcer.js中繼續 完善 match方法
/* create-matcher.js */
// 導入具體的路由解析規則
import createRouteMap from './create-route-map'
// 導入 createRoute
import createRoute from './util/route'
// 導出解析路由規則 傳入的是規則
export default function createMatcher(router) {
// pathList 路由的列表 pathMap 路由與組件的對應關係 nameMap這裡沒有考慮,先完成個簡單的
// 具體的解析規則是使用 createRouteMap
const { pathList, pathMap } = createRouteMap(router)
// match是 從pathMap 根據path獲取 相應的路由記錄
function match(path) {
// 取出path對應的記錄
const record = pathMap[path]
// 判斷記錄是否存在
if (record) {
return createRoute(record, path)
}
return createRoute(null, path)
}
// 添加動態路由
function addRoutes(router) {
// 添加動態路由肯定也要解析路由規則
createRouteMap(router, pathList, pathMap)
}
// 返回match 和 addRoutes
return {
match,
addRoutes,
}
}
複製代碼
6.歷史記錄的處理 History
在 history目錄下新建一個 base模塊用來編寫 父類
這個父類有 hash 模式 和 history(html5) 模式共同的方法
這裡就主要演示下 hash 模式的代碼
/* history/base.js */
// 導入 我們上面寫好的 createRoute
import createRoute from '../util/route'
// 導出 History
export default class History {
// router 是路由對象 也就是 VUe-Router的一個實例
constructor(router) {
// 賦值給自己的 router
this.router = router
// 默認的的當前路徑為 /
this.current = createRoute(null, '/')
}
// 將要跳轉的鏈接
// path 是路由的地址, onComplete是一個回調
transitionTo(path, onComplete) {
// 獲取當前的應該跳轉的路由 調用的是 Vue-Router中 this.matcher中收到的match方法
// 在這裡 this.router就是 Vue-Router的一個實例 所以寫成
// this.router.matcher.match(path)
this.current = this.router.matcher.match(path)
// 回調存在觸發回調
onComplete && onComplete()
}
}
複製代碼
編寫 HashHistory 模式 繼承 History
/* /history/hash */
// 導入 base中的 History
import History from './base'
// 繼承了 History
export default class HashHistory extends History {
constructor(router) {
super(router)
// 確保第一次訪問的時候路由加上 #/
ensuerSlash()
}
// 監聽URL的改變 設置當前的current
setUpListener() {
// 監聽 hash的變化
window.addEventListener('hashchange', () => {
// 改變 this.current
this.transitionTo(this.getCurrentLocation())
})
}
// 獲取當前的URL的hash 當然這裡要去除 #
getCurrentLocation() {
// 這裡不建議寫成這個 return window.location.hash.slice(1) 有兼容問題
let href = window.location.href
const index = href.indexOf('#')
// 當沒有 #的時候 直接返回 空字符串
if (index < 0) return ''
// 獲取 #後面的地址
href = href.slice(index + 1)
return href
}
}
// 確保第一次加上 #/
function ensuerSlash() {
// 如果存在 hash的話就不行加 /
if (window.location.hash) {
return
}
// 如果沒有hash值 只要給 hash 加上一個 / 它會自動加上 /#/
window.location.hash = '/'
}
複製代碼
關於 html5模式 這裡 就沒寫了
然後回到 index.js 就是自己寫的 Vue Router中繼續編寫模式判斷
最後就是 初始化 init方法
/* index.js */
// 導入 install
import install from './install'
// 導入解析路由
import createMatcher from './create-matcher'
// 導入 HashHistory
import HashHistory from './history/hash'
// 導入 HTML5History
import HTML5History from './history/html5'
// 導出自己寫的 VueRouter
export default class VueRouter {
// 實現構造函數功能
constructor(options) {
// 獲取options中的routes路由規則 沒有就為空數組
this._routes = options.routes || []
// 解析路由 傳入規則 這裡還返回了兩個方法 match,addRoutes 用matcher接收一下之後有用
this.matcher = createMatcher(this._routes)
// 獲取模式 沒有就默認為 hash 模式
this.mode = options.mode || 'hash'
// 使用 if 或者 分支都行 根據不同的模式執行不同的路由跳轉功能等等
switch (this.mode) {
case 'history':
this.history = new HTML5History(this)
break
case 'hash':
// 模式的實例使用 this.history接收等下用的上
// 傳入的this是 VueRouter
this.history = new HashHistory(this)
break
default:
throw new Error('該模式不存在')
}
}
// 初始化
init(Vue) {
// 拿到模式的實例
const history = this.history
// 進行跳轉 第一個參數是path ,第二個是回調函數
history.transitionTo(history.getCurrentLocation, () =>
// 監聽URL的改變 設置當前的 this.current
history.setUpListener()
)
}
}
// 為VueRouter 添加 install方法
VueRouter.install = install
複製代碼
7.定義一個響應值 _route
渲染不同路由頁面有個前提的就是需要一個表示 當前路由 響應式的屬性
所以我們來到 install.js 添加一個響應式的 屬性**_route**
和這個無關的代碼 …省略
/* install.js */
export let _Vue = null
export default function install(Vue) {
_Vue = Vue
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
...
// 創建一個代表當前路由 響應式的值_route
// 其實不建議使用 defineReactive直接創建。。
// 第一個參數是綁定在誰身上,第二是值名稱,第二個是值
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
...
}
},
})
}
複製代碼
然後得回到 history下面的 base 添加一個修改響應式 _route的值的回調 this.cb
/* history/base.js */
import createRoute from '../util/route'
export default class History {
constructor(router) {
...
// cb 一個回調函數,它的作用就是修改 響應式路由的值_route ,對應的視圖然後就刷新
this.cb = null
}
// 通過 listen來修改 cb的值
listen(cb) {
this.cb = cb
}
transitionTo(path, onComplete) {
...
// cb 存在就修改響應式路由的值
this.cb && this.cb(this.current)
...
}
}
複製代碼
最後在 index.js 的 init 調用 listen 方法 傳入回調修改 響應式值**_route**
/* index.js */
...
export default class VueRouter {
...
init(Vue) {
...
// 修改 響應式的 route
history.listen((route) => {
Vue._route = route
})
}
}
...
複製代碼
8.添加 $router 和 $route
我們知道在 Vue Router 提供了 $router (這個是路由對象是**Vue Router**的實例) 還有 $route(路由規則對象)
我們自己可以來到 install.js 中進行 添加這兩個屬性
/* install.js */
...
export default function install(Vue) {
...
// 添加 $router 路由對象 Object.defineProperty 參數分別是 為誰添加,屬性名,屬性值
Object.defineProperty(Vue.prototype, '$router', {
get() {
// this._routerRoot代表的是 Vue ,他的_router是 Vue Router實例
// 可以回過去看看第二點
return this._routerRoot._router
},
})
// 添加 $route
Object.defineProperty(Vue.prototype, '$route', {
get() {
// 他的_route是就是剛才添加 響應式 的當前 路由
return this._routerRoot._route
},
})
}
複製代碼
9.router-link
基本的介紹就不多說了,之前也是有介紹的。然後現在重新來實現下
在 components 文件下新建 link.js
/* ./components/link.js */
// 導出 link
export default {
props: {
to: {
type: String,
required: true,
},
},
// 渲染
render(h) {
// 轉化為虛擬DOM
return h(
// 標籤名
'a',
// 標籤屬性
{
domProps: {
href: '#' + this.to,
},
},
// 標籤裡面的內容 這裡是 默認插槽
[this.$slots.default]
)
},
}
複製代碼
10.router-view
在 components 文件下新建 view.js 具體步驟幹了什麼都寫在注釋里了
/* ./components/link.js */
// 導出 view
export default {
render(h) {
// 獲取路由規則對象
const route = this.$route
// 定義一個變量,用來等下 取 matched 中的值
let depth = 0
// 該組件為 router-view
this.routerView = true
// 嘗試去獲取父組件
let parent = this.$parent
// 判斷是否有父組件
while (parent) {
// 判斷該組件是否為 routerView
if (parent.routerView) {
depth++
}
// 繼續向上判斷還有無父組件
parent = parent.$parent
}
// 這裡的route是 this.$route 就是 _route 響應式值,也就是 current
// 當初 current 是 調用了 match方法 獲取到的 返回值是 matched 和 path
// matched 裡面是多個路由對象 是這種模式保存 [parentRecord,childRecord]
// 通過 變量depth取出來 舉個栗子 ['/login','/login/tab']
// 因為使用的unshif添加後面的父組件添加到前面
// depth 一直加 ,直接取出後面即可
const record = route.matched[depth]
// 沒有記錄直接渲染
if (!record) {
return h()
}
// 有的話就獲取記錄中的組件
const component = record.component
// 最後把組件渲染
return h(component)
},
}
複製代碼
好了到了這裡 Vue Router的第二次編寫就完成了,雖然和官方的差距很大。。額,因為這裡是簡化寫的
11.文件目錄
忘了最後貼上文件的目錄

這個模擬Vue Router的demo 放在了 github,有需要的可以這裡 MyVueRouter
到了這裡也只是僅僅實現了 VueRouter的一小部分功能
但是大體上的功能都差不多實現了,嵌套路由 添加動態路由也實現了
其實我覺得到這裡了也可以了,不過還是得繼續加油學習
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/233366.html