手寫vue-router核心原理
阿新 • • 發佈:2022-12-07
最近也在觀察vue3新特性,抽空玩一玩巢狀路由的vue-router,直接上程式碼
專案目錄結構
程式碼展示
- app.vue
<template> <div id="app"> <div> <router-link to="/">Index</router-link> | <router-link to="/person">Person</router-link> | <router-link to="/person/info">PersonInfo</router-link> </div> <!-- 一級路由 --> <router-view /> </div> </template> <style> #app{ display: flex; flex-direction: column; align-items: center;} </style>
- Index.vue
<template>
<div class="index">
<h1>this is index page</h1>
</div>
</template>
- Person.vue
<template> <div class="person"> <h1>this is person page</h1> <!-- 二級路由 --> <router-view /> </div> </template>
- PersonInfo.vue
<template>
<div class="personInfo">
<h2>this is personInfo page</h2>
</div>
</template>
js檔案
- main.js
import Vue from 'vue' import App from './App.vue' import router from './router' new Vue({ router, render: h => h(App) }).$mount('#app')
- babel.config.js
- 需要新增babel依賴支援新語法,如可選鏈
npm install --save-dev @babel/core @babel/cli
npm install --save-dev @babel/plugin-proposal-optional-chaining
module.exports = {
presets: [ '@babel/preset-env' ],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
- router目錄下檔案
- index.js
import Vue from "vue";
import VueRouter from "./vue-router";
import Index from "../views/Index.vue";
import Person from "../views/Person.vue";
import PersonInfo from "../views/PersonInfo.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Index",
component: Index
},
{
path: "/person",
name: "Person",
component: Person,
children:[
{
path: "/person/info",
name: "PersonInfo",
component: PersonInfo
}
]
}
];
const router = new VueRouter({
routes
});
export default router;
- vue-router.js
這裡先借助Vue的工具Vue.util.defineReactive
實現資料響應式,後續再手撕這個庫
import routerLink from "./router-link";
import routerView from "./router-view";
let Vue;
class VueRouter {
constructor(options) {
this.$options = options
this.current = window.location.hash.slice(1) || "/"
// 設定響應式陣列資料
Vue.util.defineReactive(this, "routerArray", [])
// 監聽hash值變化
window.addEventListener("hashchange", this.hashChange.bind(this))
this.getRouterArray()
}
hashChange() {
this.current = window.location.hash.slice(1) || "/"
this.routerArray = []
this.getRouterArray()
}
getRouterArray(routes) {
routes = routes || this.$options.routes
for (const route of routes) {
if (this.current === '/' && route.path === '/') {
this.routerArray.push(route)
return
}
if (this.current.indexOf(route.path) !== -1 && route.path !== '/') {
this.routerArray.push(route)
if (route.children) {
// 遞迴子路由
this.getRouterArray(route.children)
}
return
}
}
}
}
VueRouter.install = function(_Vue) {
Vue = _Vue
// Vue全域性混入,等new Vue中的router例項建立之後掛載到Vue上
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
},
});
// 註冊router-link和router-view全域性元件
Vue.component("router-link", routerLink)
Vue.component("router-view", routerView)
}
export default VueRouter
參考 前端面試題詳細解答
- router-link.js
export default {
props: {
to: {
type: String,
required: true
}
},
render(h) {
return h(
"a",
{
attrs: {
href: "#" + this.to,
},
},
this.$slots.default
);
}
};
- router-view.js
export default {
render(h) {
// 設定巢狀路由標識
this.$vnode.data.rv = true
// 巢狀路由設定深度
let depth = 0
let parent = this.$parent
while (parent) {
// 上級還有巢狀路由標識rv為true的,深度加一
if (parent.$vnode?.data?.rv) {
depth++
}
parent = parent.$parent
}
// 簡單處理
const route = this.$router.routerArray[depth]
return h(route?.component);
}
};
- 效果圖
好了,今天就玩到這裡了,下次再玩別的哈