preface
This paper is only a demo, and there are many functions that have not been realized. Several articles will be published later to complete the core functions of vue-Router.
Core functions that have been implemented
router-link
androuter-component
$router
and$route
hash mode
andhistory mode
- Switch components that listen for route changes
Then consider the functionality implemented
- Embedded routines by
- Dynamic Route Matching
thinking
When you install vue bucket via vue-CLI, you open SRC /router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from ".. /views/Home.vue";
// Think 1: Why use vue.use
Vue.use(VueRouter);
const routes = [
{
path: "/".name: "Home".component: Home,
},
{
path: "/about".name: "About".// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () = >
import(/* webpackChunkName: "about" */ ".. /views/About.vue"),},];// Reflection 2: Why new VueRouter
const router = new VueRouter({
mode: "history".base: process.env.BASE_URL,
routes,
});
export default router;
Copy the code
Drive the SRC/main points again. Js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
// Think 3: Why is the root instance passed to the router
new Vue({
router,
store,
render: h= > h(App)
}).$mount('#app')
Copy the code
It opens at the SRC/App. Vue
<template>
<div id="app">
<div id="nav">
<! Router-view/router-link/router-view/router-view/router-view/router-view/router-view
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
<script>
export default {
methods: {
xx() {
// Think 5: Why can I access the $router from any component and the $router is the same thing
this.$router.push("/")
}
},
}
<script>
Copy the code
Thought 6: Why do I click router-link to switch to a view page but it doesn’t jump and refresh
My own thinking
- Use it is clear that VueRouter is a plug-in, and we need to implement an install method to install the plug-in
- New VueRouter({}) shows that VueRouter is a class, or constructor
- This is kept in suspense and will be answered later.
- Use (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter)}}
- Obviously, any component can access the same thing, indicating that the $router is mounted on vue. prototype.
- Router-link is essentially an A tag, but the default jump event is intercepted and instead switches the components rendered by the Router-View
The body of the
Create the mRouter. Js folder under SRC/Router to write our own VueRouter class.
1. The original look
1) First define the VueRouter class
2) Implement the install method, and register the two router-link and router-view components globally
3) Router-link and router-view these two things are arbitrary, I will improve them in the following
src/router/mRouter.js
class VueRouter {
constructor() {}
}
VueRouter.install = function(vue) {
vue.component("router-link", {
render(h) {
return h("a", {}, this.$slots.default); }}); vue.component("router-view", {
render(h) {
return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code
The effect
2. Mount $router
Q: As mentioned above, we want to mount $router on vue. prototype and we have a problem: The router is passed in new Vue({router}), and if vue.use (VueRouter) is executed before new Vue({}), then we cannot access the Vue instance in Install and therefore cannot mount it.
Faced with this problem, let’s think about what we expect. We want to defer execution to some point in the future, specifically when the root instance of new Vue({}) is created.
Implementation of the method we can look at the source code how to do
For each beforeCreate instance, determine whether it is the root instance for each beforeCreate instance. For each beforeCreate instance, the router attribute is displayed in the options for the root instance. For each beforeCreate instance, the router attribute is displayed for each beforeCreate instance.
Their implementation
VueRouter.install = function(vue) {
vue.mixin({
beforeCreate() {
if (this.$options.router ! = =undefined) {
vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
props: ["to"].render(h) {
return h("a", { attrs: { href: this.to } }, this.$slots.default); }}); vue.component("router-view", {
render(h) {
// Print it
console.log("test-router--".this.$router);
return h("div", {}, "renderrender"); }}); };Copy the code
The effect
3. Create a response between URL and view
Here we need to use a variable current to record the current URL. When the URL changes, we update current as the view changes.
We have two modes: history and hash. We listen for popState and hashchange events respectively and update current when triggered.
The next question arises, if we change current directly, will the render function know that current is updated? Let’s try it out.
First we switch mode to hash mode because we haven’t done interception in history mode yet.
class VueRouter {
constructor(options) {
this.$options = options;
this.current = "/";
const strategy = {
hash: () = > {
window.addEventListener("hashchange".() = > {
// '#/about/id=1123' => no # needed
this.current = window.location.hash.slice(1);
});
},
history: () = > {
window.addEventListener("popstate".() = > {
// / ABC is what we want
this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
vue.mixin({
beforeCreate() {
if (this.$options.router ! = =undefined) {
vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
props: ["to"].render(h) {
let prefix = this.$router.$options.mode === "hash" ? "#" : "";
return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
render(h) {
// Let's click on different urls to see if console fires
console.log("render---".this.$router.current);
return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code
Obviously the Render function has no idea that current has changed, so it only executes once.
Anyone who has ever used Vue knows how to handle this, we just need to make current responsive, and Vue will collect the dependency and trigger the corresponding Render function when current updates.
And here comes the question, how do we convert this into a responsive form. $set, object.defineProperty, new Vue({data():{return current:”/”}}) or something else?
$set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set Here, we don’t even have a reactive object, so how do we use $set?
The second way you can try to do it yourself, the third way is feasible, but we won’t use it here.
Var ue.util. DefineReactive is a utility function in Vue that helps us to create a reactive attribute.
/** * Define a reactive property on an Object. */
function defineReactive$$1 (obj, key, val, customSetter, shallow) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if((! getter || setter) &&arguments.length === 2) {
val = obj[key];
}
varchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) { dependArray(value); }}}return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if(getter && ! setter) {return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
Copy the code
Let’s use this defineReactive to see if this works
let Vue = null;
class VueRouter {
constructor(options) {
this.$options = options;
// The Vue here is obtained from install below
Vue.util.defineReactive(this."current"."/");
const strategy = {
hash: () = > {
window.addEventListener("hashchange".() = > {
// '#/about/id=1123' => no # needed
this.current = window.location.hash.slice(1);
});
},
history: () = > {
window.addEventListener("popstate".() = > {
// '#/about/id=1123' => no # needed
this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
Use () and then new VueRouter(), so we can access Vue from constructor
Vue = vue;
vue.mixin({
beforeCreate() {
if (this.$options.router ! = =undefined) {
vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
props: ["to"].render(h) {
let prefix = this.$router.$options.mode === "hash" ? "#" : "";
return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
render(h) {
console.log("render---".this.$router.current);
return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code
We’re just one step away from matching the path in the routing table with current and rendering the corresponding Component. I’m not considering nested routing and dynamic routing.
let Vue = null;
class VueRouter {
constructor(options) {
this.$options = options;
Vue.util.defineReactive(this."current"."/");
// this.current = "/";
const strategy = {
hash: () = > {
window.addEventListener("hashchange".() = > {
// '#/about/id=1123' => no # needed
this.current = window.location.hash.slice(1);
});
},
history: () = > {
window.addEventListener("popstate".() = > {
// '#/about/id=1123' => no # needed
this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
Vue = vue;
vue.mixin({
beforeCreate() {
if (this.$options.router ! = =undefined) {
vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
props: ["to"].render(h) {
let prefix = this.$router.$options.mode === "hash" ? "#" : "";
return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
render(h) {
console.log("render---".this.$router.current);
const component = this.$router.$options.routes.find((item) = > {
return item.path === this.$router.current; })? .component ?? {render(h) {
return h("div", {}, "404"); }};returnh(component); }}); };export default VueRouter;
Copy the code
conclusion
In fact, this part of the content of a lot of big estimate all know, the dish chicken is also recently began to see the source code, follow the big thinking go, and then put forward in accordance with the problem to think how to solve the problem step by step. Really can’t think of or gnaw the source code, after all, the standard answer is there.
reference
Follow along, you can also hand write the VueRouter
From routing to vue-router source code, take you through the front-end routing
Vue Router