1, the preface
This article introduces the implementation principle of VueRouter step by step. Before reading this article, you need to have a basic understanding of the use of vUE, and learn the basic concepts of class.
Implementation notes:
- How to register a plug-in
- Realize router-view and router-link components
- How do I display components based on the current route
- How to update components during route switching
- How to implement nested routines
So with that in mind, let’s start implementing it step by step
Final code link
Making a link
2. Prepare test data
We can use VueCli to build a project for VueRouter. Here’s a brief description of commands
# If you use YARN
yarn global add @vue/cll
# If you use NPM
npm install -g @vue/cli
Create a Vue2 project
vue create vue-router-study
Copy the code
After the installation, start the service directly
cd vue-router-study
yarn serve
Copy the code
We can then run a test example using the official Vue-Router
yarn add vue-router
Copy the code
Write the file router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
export default new VueRouter({
routes: [{path: '/'.component: () = > import('.. /components/HelloWorld.vue'),}, {path: '/a'.component: () = > import('.. /components/A.vue'),
children: [{ path: '/a/b'.component: () = > import('.. /components/B.vue')}],},],})Copy the code
Edit main.js to add the router to the Vue option
import router from './router'
// ...
new Vue({
router,
render: h= > h(App),
}).$mount('#app')
Copy the code
Display our data in app.vue
<template>
<div id="app">
<div>
<router-link to="/">Home page</router-link>
</div>
<div>
<router-link to="/a">A page</router-link>
</div>
<div>
<router-link to="/a/b">B page</router-link>
</div>
<router-view></router-view>
</div>
</template>
// ...
Copy the code
Create two components a. vue and B.vue
components/A.vue
<template>
<div>I'm component A<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "A"
};
</script>
Copy the code
componets/B.vue
<template>
<div>I'm page B</div>
</template>
<script>
export default {
name: "B"
};
</script>
Copy the code
Now go back to the page and see what it looks like
Now I’ll start implementing my own vue-Router plug-in
3. Implement plug-in registration
When we use VueRouter, we register with use, indicating that VueRouter is a plug-in. An install method needs to be implemented
Create a new file to implement our own VueRouter
Create a VueRouter class, write an Install method, and define a variable to hold Vue
src/avue-router.js
let Vue
class VueRouter {}
VueRouter.install = function(_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate() {
if (this.$options.router) Vue.prototype.$router = this.$options.router
},
})
}
export default VueRouter
Copy the code
Use our own avue-router.js on router/index.js
// import VueRouter from 'vue-router'
import VueRouter from '.. /avue-router'
Copy the code
Go back to the page and see if it displays properly. If successful, the plug-in is registered successfully
When vue.use (VueRouter), Vue automatically calls the install scheme. Using mixins, we can mount our router instance in the Vue option to the prototype and get the instance data from this.$router in the Vue instance
4. Implement router-link components
Mount the global component through Vue and concatenate props into href, filling in the values of the default slots
VueRouter.install = function(_Vue) {
// ...
Vue.component('router-link', {
props: {
to: {
type: String.require: true,}},render(h) {
return h(
'a',
{
attrs: {
href: The '#' + this.to,
},
},
this.$slots.default
)
},
})
}
Copy the code
Now back on the page, router-link is displayed normally.
5. Implement router-view component
You need to declare a reactive variable current to hold the current hash path. The router-view component matches the component in the Routes table based on this path and is displayed. And listens for hashchange to update the current path when the path is updated.
// ...
class VueRouter {
constructor(options) {
// Save instance configuration items
this.$options = options
Vue.util.defineReactive(this.'current'.window.location.hash.slice(1) | |'/')
addEventListener('hashchange'.this.onHashChange.bind(this))
addEventListener('load'.this.onHashChange.bind(this))}onHashChange() {
this.current = window.location.hash.slice(1) | |'/'
}
}
VueRouter.install = function(_Vue) {
// ...
Vue.component('router-view', {
render(h) {
let component = null
const route = this.$router.$options.routes.filter(route= > route.path === this.$router.current)[0]
if (route) component = route.component
return h(component)
},
})
}
Copy the code
Since we are not implementing nested routines at the moment, we need to comment out the router-view in a. ue, otherwise it will cause an infinite loop
components/A.vue
<template>
<div>I'm component A<div>
<! -- <router-view></router-view> -->
</div>
</div>
</template>
<script>
export default {
name: "A"
};
</script>
Copy the code
Now that we’re back on the page, we see that we can switch pages using router-link.
5.1 Implement nested routines by
Let’s look at the official notation
As can be seen, he defines a depth variable for each router-view component to determine its depth, and has a matched array, which records the corresponding route array of the current path.
So, for example, our hash address is/A /b, and matched should be
let matched = [
{
path: '/a'.component: () = > import('.. /components/A.vue'),
children: [{ path: '/a/b'.component: () = > import('.. /components/B.vue')}],}, {path: '/a/b'.component: () = > import('.. /components/B.vue'),},]Copy the code
Now we implement the matched method in the VueRouter class. Through a recursive route table. Collect all route arrays for the current path.
class VueRouter {
// ...
match (routes) {
routes = routes || this.$options.routes
for (const route of routes) {
// If it is the root directory, push a route into it and return it directly
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
// If it is not the root directory, determine all routes contained in the current directory and collect them
if(route.path ! = ='/' && this.current.indexOf(route.path) ! = = -1) {
this.matched.push(route)
if (route.children) this.match(route.children)
}
}
}
}
Copy the code
Next we’ll rewrite the Route-View component. Add routerView attributes to each Route-View component to determine whether it is a Router-View component. Starting with the current instance, loop up to figure out what level the current component is at. Save the value to the depth variable. Then select route of corresponding layer according to matched table to obtain component display of the table.
VueRouter.install = function (_Vue) {
// ...
Vue.component('router-view', {
render (h) {
this.$vnode.data.routerView = true
let depth = 0
let parent = this.$parent
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data
if (vnodeData && vnodeData.routerView) {
depth++
}
parent = parent.$parent
}
let component = null
const route = this.$router.matched[depth]
if (route) component = route.component
return h(component)
}
})
}
Copy the code
Now we can unpack the router-view comment in Components /A.vue. Display normal ~