1. Basic review of Vue-Router
(1) Basic use
- Register routing plug-in
- Create routing instance
router/index.ts
// 1. 注册路由插件
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// 使用import动态懒加载组件,提升首页渲染效率
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
// 2. 创建路由实例
const router = new VueRouter({
routes
})
- When creating
Vue
an instance, hang it in the routing instance
main.ts.
// 3. 创建 Vue 实例并挂载路由实例
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router
The act of passing it to Vue
the constructor of an instance will cause the Vue
instance to hang on to the two objects
$route
: Route matching rules$router
: Routing instance object, you can callpush
,go
and other methods to change the current route.$router.currentRoute
The attribute points to the current route matching rule:
console.log(myVue.$route===myVue.$router.currentRoute) // true
- Create a route placeholder
- Create routing jump link
APP.vue
<nav>
<!--5.创建路由跳转链接-->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<!--4.创建路由占位符-->
<router-view/>
(2) Dynamic routing
Vue
Dynamic routing in refers to the fact that dynamic parameter transfer can be used to achieve jumps in the routing path url
. At this time, the routing url
path is not hard-coded but variable. Definition and value transfer of dynamic routing:
router/index.ts
{
path: '/about/:id', // :id起占位符的作用
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
APP.view
<router-link to="/about/89">About</router-link>
There are two ways to receive dynamic parameters inside a route:
- Use
$router.params
view/About.vue
<h2>id: {
{ $route.params.id }}</h2>
One disadvantage of this approach is that it can lead to over-reliance $route
.
2. Use props
receiving
router/index.ts
{
path: '/about/:id',
name: 'about',
props: true,
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
views/About.vue
<h2>id: {
{ id }}</h2>
<script>
export default {
name: 'AboutView',
props: {
id: String,
},
};
</script>
Not passing dynamic parameters at this time will cause the page to be blank. Adding it later
indicates that this attribute is optional. If this parameter is not passed, other parts of the routing component can also render router/index.ts normally.:id
?
path: '/about/:id?',
(3) Nested routing
Nested routing is used in scenarios where components are nested at multiple levels. As shown in the figure below, /user/johnny
corresponding to a component, it also contains two components that can be switched and displayed. The routes corresponding to these two components can be defined as /user/johnny
sub-routes .
The sub-routes are defined using children
the attributes of the route. children
The attributes point to an array. The array The objects in have attributes such as and , path
which represent the path of the sub-routing. There are two ways of writing:component
path
- One is the absolute path, which is defined starting from the root directory and needs to be added at the beginning
/
; - One is the relative path, which defines the path relative to the parent route. The path of the parent route and the relative path will eventually be spliced together as the path of the current subcomponent. It cannot be added at the beginning
/
.
router/index.ts
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
children: [
{
path: '/about/company', // 绝对路径
// path: 'company' // 相对路径
name: 'company',
component: () => import(/* webpackChunkName: "about" */ '../views/CompanyView.vue')
}
]
}
Define routing jump links and placeholders in the component template of the parent component
views/AboutView.vue
<router-link to="/about/company">company</router-link>
<router-view></router-view>
(4) Programmatic navigation
In addition to using a
links to implement route jumps, you can also use $router
methods on objects to implement route jumps. There are three main methods used:
push()
. It accepts two parameters. Parameter one can be written in two ways. The first is a path, and the second is an object. The object contains thepath
attributes of the route orname
the attributes of the named route. Parameter two is used to pass parameters for the route.replace()
. The usage is similar topush()
the method, but this history will not be recorded, and the routing history after the jump will replace the current routing history. That is to say, after the jump, you cannot go back to the current route through the browser's back button.go()
. This method takes an integer as a parameter, indicating how many steps forward or backward in the history stack, similar towindow.history.go(n)
.
App.vue
<template>
<div id="app">
<router-view/>
<button @click="goHome()">go HomeView</button>
<button @click="goAbout()">go AboutView</button>
<button @click="back()">回退</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
goHome() {
this.$router.push('/');
},
goAbout() {
this.$router.replace({
name: 'about' });
},
back() {
this.$router.go(-1);
},
},
};
</script>
2. History mode
(1) The difference between Hash mode and History mode
Both are front-end routes, changing the page rendering content through path changes.
Hash
The pattern is based on anchors andonhashchange
events, and front-end anchors can be implemented throughHTML
element and linkhref
attributes. When the path changes, the page rendering content is determined based on the path. The following dataHash
in the pattern represents the routing configuration#
History
The pattern is based onHTML5
mediumHistory API
and mainly usespushState()
methods andreplaceState()
methods.pushState()
The method does not send a request to the server, it only changesurl
the address and records the address in the history.
<template>
<div id="app">
<router-view/>
<button @click="goHome()">go HomeView</button>
<button @click="goAbout()">go AboutView</button>
<button @click="back()">回退</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
goHome() {
this.$router.push('/');
},
goAbout() {
this.$router.replace({
name: 'about' });
},
back() {
this.$router.go(-1);
},
},
};
</script>
(2) History working mode
History
In mode, url
the path is an ordinary address: https://www.teambition.com/project
. For single-page applications, the server only saves html
the resources of the homepage. If the server does not have the corresponding configuration, accessing the server with such an address will not find the resource; therefore, the server needs to be configured accordingly to support the History
mode. When the server finds When the requested resource is not available, the static file of the home page is returned to the client, and the client parses the page based on the current routing address to implement page jump.
- For
node
the server,history
middleware needs to be configured - For
nginx
the server, you need to modifynginx.conf
the configuration filelocation /
and add a line of code in:try_files $uri $uri/ /index.html
; It means trying to load the resource corresponding to the requested path on the server. If it cannot be found, use the address as the folder and search downindex.html
; if it still If it is not found, it returns to the root file in the websiteindex.html
.
3. Vue Router implementation
(1) Working principle of Vue Router
Hash
How the pattern works
#
The last string in the address bar represents the routing address. When only#
the last address changes, no request will be sent to the server, and the new address will be saved in the history.onhashchange
The method will monitor changes in routing addresses.- Inside
onhashchange
the method, the corresponding component will be found based on the routing address and the page will be rendered.
History
working principle
- When clicking a routing jump link,
History API
thepushState()
method adds a new record to the browser history stack and changes the current one without changing the pageURL
. - When the user clicks the browser's back button or calls a
go()
method,popState
the method will be triggered, the current address will be modified, and the execution of the methodVue-Router
will be monitoredpopState
to obtain the current routing information. - Find the corresponding component according to the current routing information for page rendering.
(2) Vue Router analysis
- Let’s first take a look
Vue Router
at the usage and analyzeVue Router
the types of
// 1. 注册路由插件
Vue.use(VueRouter)
// 2. 创建路由实例
const router = new VueRouter({
routes
})
- Register
Vue Route
r usageVue.use()
method. This method is used to register plug-ins. There are two parameter types that can be received. One is a function, which will be executed directly; the other is a class, which will execute the staticinstall()
method of the class. Vue Router
You can usenew
the keyword to create an instance object, accepting an object as a parameter.- So
Vue Router
it's a class, the constructor receives an object as a parameter, the object has a propertyroutes
; andVue Router
the class has a static methodinstall
.
Vue Router
The object needs to have three properties:
options
Save incoming routing rulesrouteMap
Save the corresponding relationship between routes and components as a mapping relationshipdata
ToVue.observable
implement two-way data update, it needs to be a responsive object defined
- According to
Vue Router
the function of the object, it can be divided into four methods:
initEvent()
RegisterpopState
an event listener for subsequent processing when routing changescreateRouteMap()
initializationrouteMap
initComponents()
Initializationrouter-link
androuter-view
componentsinit()
Execute the above three methods
(3) Install() method implementation
Create a new src/vuerouter/index.js
file and export Vue Router
a class
export default class VueRouter {
static install(){
}
}
install()
Functions that the method needs to implement:
- To determine whether the current plug-in has been installed, you need to give
install()
the method an attribute to identify it. Wheninstall()
the method is executed, this attribute will changetrue
.
if(VueRouter.install.installed) return;
VueRouter.install.installed = true;
- Receive
Vue
the constructor as a parameter andVue
record the constructor as a global variable because you need to useVue
the methods on the class later, such asVue.component()
let _Vue = null;
export default class VueRouter {
//接受Vue的构造函数作为参数
static install(Vue) {
// 1. 判断当前插件是否已经被安装,如果已经安装则不需要重复安装
if(VueRouter.install.installed) return;
VueRouter.install.installed = true;
// 2. 把Vue构造函数记录到全局变量
_Vue = Vue;
}
}
Why accept Vue
a constructor as a parameter? You can take a look at install()
the source code definition of the method:
// router.d.ts
static install: PluginFunction<never>
// plugin.d.ts
export type PluginFunction<T> = (Vue: typeof _Vue, options?: T) => void
// vue.d.ts
export const Vue: VueConstructor
Being able to know what a function looks like so easily is TypeScript
a very powerful place.
- Let’s review
Vue
the code that creates the instance:
const myVue = new Vue({
router,
render: h => h(App)
}).$mount('#app')
At this time, Vue
an object is passed to the constructor router
, and install()
the method needs to router
inject the object into Vue
the instance. What is currently passed in is Vue
the constructor. To Vue
add attributes to the instance, you need to mount the attributes on the prototype chain, and Vue
the instance object can inherit Vue
the attributes on the constructor in a chain. Vue
Parameters passed during instance construction are saved in Vue
the instance's $options
properties. But at install()
the time of execution, we don't know Vue
who the instance is yet, so we need to delay this operation and perform it during the life cycle Vue
of the instance beforeCreate()
. So you need to use mixin here to beforeCreate()
mix the method into each Vue
instance object. Vue
Moreover, this method can be accessed on all instance objects as long as it is executed once, so it only needs to be executed once in the myApp
application and does not need to be executed repeatedly in the beforeCreate()
subsequent component life cycle. Vue
The mixed hook function will be executed before the hook function of the instance itself.
_Vue.mixin({
beforeCreate() {
// 实例创建过程即new Vue()的时候有传递router参数,证明当前Vue实例是应用
if(this.$options.router) {
// this指向当前Vue实例对象
_Vue.prototype.$router = this.$options.router;
}
}
})
(4) constructor constructor
constructor
The constructor needs to initialize three properties options
: , routeMap
, and . Code reviewed :data
new VueRouter()
const router = new VueRouter({
routes
})
constructor
The constructor will accept an object as a parameter, which needs to be saved in the attribute options
; routeMap
it is a key-value pair object used to save the correspondence between the path and the component. In the initialization phase, you only need to initialize it to an empty object. The value is assigned later; data
the attribute is a responsive object, with an current
attribute pointing to the current routing address. The initialization phase is set to '/'
. You need to use the method Vue
provided by the constructor observable
to create the responsive object. When the responsive object changes, it will be automatically triggered. Rely on the functions in the component of this responsive object render
to automatically update the view.
constructor(options) {
this.options = options;
this.routeMap = {
};
this.data = _Vue.observable({
current: '/'
});
}
(5) createRouteMap() method
createRouteMap()
The method is used to create routeMap
this variable, which stores the relationship between the routing path and the component in the form of a key-value pair, with the path as the key and the component as the value.
createRouteMap() {
// 遍历所有路由规则,把路由规则解析成键值对的形式,存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component;
})
}
(6) initComponent() method
initComponent()
Methods are used to create router-link
components and router-view
components. How to use component registration Vue.component()
.
1.router-link
Let's first review router-link
the use of: <router-link to="/">Home</router-link>
. routerLink
The component receives a parameter to
and will eventually be rendered in a
the form of a label, so the template is a a
label; a
the content in the label is the content passed by the parent component when the component is used, so when defining it, you need to use the slot to first modify the label a
. The content is placed in place. From the above analysis it can be defined routerLink
:
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'
})
But there are still some problems with this way of definition. The problem lies in Vue
the build version. Vue
The build versions are divided into runtime version and full version.
- The full version includes a compiler that compiles templates at runtime. Therefore, it supports
Vue
the use of attributes in componentstemplate
and is suitable for development environments. - The runtime version does not contain a compiler and cannot use
template
attributes. However, it is more lightweight and can mostlyHTML
work by putting the template into and used in a production environment.
Vue-cli
The default is runtime Vue
, so template
attributes are not supported. There are two solutions
- Use the full version
Vue
. Add tovue.config.js
the configuration options of the fileruntimeCompiler: true,
template
Instead of using arender
function.render
The function receives ah
function as a parameter and returnsh
the result of function processing.h
The function is to create a virtualDOM
and accept three parameters- Parameter 1: Virtual
DOM
selector (label selectora
) - Parameter two: virtual
DOM
attributes (href
attributes, pointersthis.to
) - Parameter three:
DOM
an array composed of virtual sub-elements (there is only one sub-element: an anonymous slot, using the acquisitionvue
of the instance . The function is a method mounted on the instance, and internally points to the instance; if an arrow function is used, the point of point to the same, i.e. instance). Arrow functions cannot be used here to point to the created component.$slot.default
render
Vue
this
Vue
this
initComponent()
this
VueRouter
this
routerLink
- Parameter 1: Virtual
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
attrs: {
href: this.to
}
}, [this.$slots.default])
}
})
2.router-view
routerView
The component needs to find the component corresponding to the current route and then render this component. routerMap
The corresponding relationship between routing and components is stored in the attribute. The data
attribute stores the current route, so you need to this.routeMap[this.data.current]
obtain the component that currently needs to be rendered and pass the component to the h function.
Vue.component('router-view', {
// 注意使用箭头函数
render:(h) =>{
const component = this.routeMap[this.data.current];
// h函数会帮我们把组件转化为虚拟DOM
return h(component);
}
})
In router-link
, we also need to improve the click event processing function.
- When a link is clicked
a
, a request will be sent to the server by default to requesta
the resource pointed to by the link. Therefore, in the handler function of the click event, the default behavior should be prevented; - Let's review
this.data
the attribute. It is a responsive object, in whichcurrent
the attribute points to the routing address. When thedata.current
attribute is modified, it will causerouter-view
therender
function to be re-executed. BecauserouterView
the component depends on it , it triggers the view update, so the attributesthis.data.current
of the current route need to be modified as , Let the view update automatically.data.current
this.to
- Not only do you need to modify the view, but you also need to modify the address bar address, so you need to call
window.history
apushState()
method to modify the address bar address.
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.handleClick
}
}, [this.$slots.default])
},
methods: {
handleClick(e) {
// 改变current的值,触发组件的重新渲染
// current是一个响应式的数据,所以会触发render函数的重新执行
// render函数将会重新生成href指向this.to的a标签
this.$router.data.current = this.to;
// 通过pushState改变地址栏的地址,但是不会向后端发送请求
// 参数一:state对象,可以在触发popstate事件时获取到
// 参数二:title
// 参数三:url
history.pushState({
}, '', this.to);
// 点击a链接默认会向服务端发送请求去请求a链接对应的数据
// 具体表现为页面会刷新,
// 这里要阻止向服务端发送请求
e.preventDefault();
}
}
})
In this way, the function of clicking the routing link to realize the routing jump has been generally realized, but there is still a problem. When clicking the forward and back buttons of the browser, only the address bar path changes, and the view is not refreshed.
3.initEvent()
When the browser's forward and back buttons are clicked, popState()
the event listening function will be triggered, so page refresh needs to be handled in this method. In popState()
, the address bar has been updated, so the routing address can be obtained directly from the address in the address bar. The value assigned to is this.data.current
window.location
an object containing the current URL information, which is Location
an instance of the object. This object has many properties and methods, the following are some commonly used ones:
location.href
: Returns the complete URL.location.protocol
: Returns the protocol used by the current page (such as: http, https).location.host
: Returns the host name (domain name) and port number of the current page.location.pathname
: Returns the path part of the current page.location.search
: Returns the query string part of the current page.location.hash
: Returns the anchor part of the current page.
initEvent() {
// 监听浏览器地址栏地址的变化
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname;
})
}
Don’t forget init()
to call initEvent()
functions within functions
init() {
this.createRouteMap();
this.initComponents(_Vue);
this.initEvent();
}
VueRouter
Introduce your own router/index.js where introducedVueRouter
import VueRouter from '../vuerouter'
4. Complete code
vuerouter/index.js
let _Vue = null;
export default class VueRouter {
//接受Vue的构造函数作为参数
static install(Vue) {
// 1. 判断当前插件是否已经被安装,如果已经安装则不需要重复安装
if (VueRouter.install.installed) return;
VueRouter.install.installed = true;
// 2. 把Vue构造函数记录到全局变量
_Vue = Vue;
// 3. 把创建Vue实例时候传入的router对象注入到Vue实例上
_Vue.mixin({
beforeCreate() {
// this是Vue实例
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router;
this.$options.router.init();
}
}
})
}
// 构造函数
constructor(options) {
this.options = options;
this.routeMap = {
};
this.data = _Vue.observable({
current: '/'
});
}
init() {
this.createRouteMap();
this.initComponents(_Vue);
this.initEvent();
}
createRouteMap() {
// 遍历所有路由规则,把路由规则解析成键值对的形式,存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component;
})
}
initComponents(Vue) {
// 将Vue实例传过来是为了减少方法与外部的依赖
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.handleClick
}
}, [this.$slots.default])
},
methods: {
handleClick(e) {
// 改变current的值,触发组件的重新渲染
// current是一个响应式的数据,所以会触发render函数的重新执行
// render函数将会重新生成href指向this.to的a标签
this.$router.data.current = this.to;
// 通过pushState改变地址栏的地址,但是不会向后端发送请求
// 参数一:state对象,可以在触发popstate事件时获取到
// 参数二:title
// 参数三:url
history.pushState({
}, '', this.to);
// 点击a链接默认会向服务端发送请求去请求a链接对应的数据
// 具体表现为页面会刷新,
// 这里要阻止向服务端发送请求
e.preventDefault();
}
}
})
// router-view起一个占位符的作用
// 首先要在routeMap找到目标组件
// 然后渲染组件
Vue.component('router-view', {
render:(h) =>{
const component = this.routeMap[this.data.current];
// h函数会帮我们把组件转化为虚拟DOM
return h(component);
}
})
}
initEvent() {
// 监听浏览器地址栏地址的变化
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname;
})
}
}