整理一下在拉勾前端训练营的Vue的知识,所以写下这篇文章。
回顾
Vue-Router是Vue中常用的插件。先简单回顾一下Vue-Router是做什么和怎样用。
Vue-Router是官方的路由器,用来构建单页面应用。所谓单页面应用,就是当用户点击超链接时,浏覧器不会向服务端发起请求,然后跳转到下一个网页,而是如平常的桌面的应用般,自然切换界面。
简单回顾下如何使用。首先用vue的脚手架创建项目,该项目要有vue-router插件,然后在src/view里添加一些vue文件作为界面,然后在src/router里建立index.js,构建路由。代码如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/blog',
name: 'Blog',
// 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('../views/Blog.vue')
},
{
path: '/photo',
name: 'Photo',
// 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('../views/Photo.vue')
}
]
const router = new VueRouter({
routes
})
export default router
首先Vue要注册一个Vue-Router插件,创建路由规则,component用箭头函数动态加载作为view的vue文件,即当切换至该页面才加载。最后创建一个VueRouter实例,引入路由规则,输出实例。项目的主文件中引入路由器。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
关于路由JS的部分已经完成,剩下就是在html显示出来,这要用到router-link和router-view。router-link类似于超链接,不同的是它不会使页面跳转,而是根据它指向的路径切换router-view的界面。 html页面中的路由部分如下所示:
<div>
<router-link to="/">Index</router-link> |
<router-link to="/blog">Blog</router-link> |
<router-link to="/photo">Photo</router-link>
</div>
<router-view/>
Vue-Router插件实现
Install
现在开始实现插件。在Vue里,所有的插件都必须有install静态方法,这样Vue才能通过执行它,加载插件。我们可以把install理解为一种规范,或如果在面向对象语言,则是以接口形式出现。 实现install方法前,看一下加载了路由器的Vue实例有什么特别。 首先会在options属性里,多了router,而里面又有一个options,含有routes属性,它带有我们之前设定的路由规则。
所以install要做的是三件事:
- 判断是否安装。如果安装了,则不用再安装
- 把引入的Vue作为全局对象
- 把Vue实例所加载的路由注册至全局的Vue
第一件事比较容易实现,我们只要用一个boolean变量和判断就能做到。
class VueRouter {
static install(Vue) {
if(VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
}
}
如果有一个VueRouter.install.installed为true,表示已经安装了,退出方法。 我们在class外面定义一个变量,之后可以全局引用传来的vue实例。
let _vue = null
...
_vue = Vue
接下来要做的是把vue实例的router引入至vue。这要怎样做呢? 这里要运用mixin(混入)。所谓混合,就是把一部分功能拿出来,可以在不同的vue实例中重複使用。
...
_Vue.mixin({
beforeCreate(){
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
}
}
})
...
我们用mixin全局注册一个生命周期事件,当一个vue实例创建前,看一下当前实例有没有router属性,有的话,则在原型上添加该路由,全局可以引用。$options是用来读取vue实例中自定义的属性,自定义的属性都放在options属性里。注意一下这里的this,这里的this是vue实例的this,而不是vue-router,因为this的指向是以引用时的位置决定,beforeCreate是在vue实例中执行,所以是指向该vue实例。
Constructor 构造函数
现在开始写构造函数。代码如下:
constructor(options){
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current:"/"
})
}
构造函数的形参是一个对象,传来一系列属性,如mode和routes等,routeMap用来保存路径与组件之间的映射,data保存当前路径,使用Vue的observable方法,把它变成响应式数据,如果data发生变化,vue会自动作出相应更新。 之后我们要研究Vue-Router的初始化方法。
createRouteMap
这个方法是解析options,建立路径与组件之间的映射,存入routeMap。
createRouteMap() {
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponent(Vue)
initComponent创建router-link和router-view组件。router-link组件在浏覧器显示的是超链接,所以我们大概知道这组件要怎样写。
initComponent(Vue) {
_Vue.component("router-link", {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'
})
}
不过这样写,当一在html里使用,发生报错,原因是Vue的脚手架生成的Vue模块不是完整版的,运行时没有编译器进行编译 (平时我们写的vue文件,运行项目前已经预先编译)。解决方法有两种,一是添加一个运行时编译器,不过要多加载10kb内容,二是用渲染函数。这里用第二种方法:
initComponent(Vue){
Vue.component("router-link",{
props:{
to:String
},
render(h){
return h("a",{
attrs:{
href:this.to
},
on:{
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
history.pushState({},"",this.to)
this.$router.data.current=this.to
e.preventDefault()
}
}
})
渲染函数render有一个参数,是一个函数,它用来创造元素,它接收三个参数,第一个是创造元素类型,这里是'a',第二个是元素的设定,如属性,方法等,它是一个对象。attrs里可以设定元素的属性,而on可以挂载方法。attrs的不多说,一目了然,重点说一下on里的方法。
当用户点撃router-link,不会发生跳转,但浏覧器的路径发生改变,而且router的data属性也要改变。不发生跳转可以用preventDefault,而data修改为to的值就可以了。浏覧器的路径要怎样发生改变?可以使用history.pushState。
history.pushState简单来说,就是为浏覧器历史添加记录。前两个参数用不上就不管,关键是最后一个,它可以修改浏覧器路径。
好啦,现在router-link的属性和方法已经设定好,最后一个参数可以传入一个数组,作为文本的值。this.$slots.default可以获取没有名字的插糟的默认值。
最后创建router-view组件,它的作用就是把当前路径对应的组件渲染出来。 我们要做的事:
- 找出当前路径对应的组件
- 渲染组件出来 第一件简单,用this.data.current得到当前路径,然后在routeMap得到组件。 第二件事可以用渲染函数直接把组件渲染出来。
const self = this
Vue.component("router-view",{
render(h){
const cm=self.routeMap[self.data.current]
return h(cm)
}
})
注意self是保存Vue-Router的引用,因为在Vue.component的this是指向Vue。
initEvent
Vue-Router插件基本已经完成,然而如果用户返回前一页,浏覧器的界面没有什何反应,因为浏覧器只是改变路径,所以我们还需要加上事件,使vue有所反应,所应渲染界面,而popstate则是为此而生,当返回上下页,调用popState注册的事件。
initEvent(){
window.addEventListener("popstate",()=>{
this.data.current = window.location.pathname
})
}
init
基本所有初始化方法已经完成,用init方法封装。
init(){
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
最后放在构造函数里。简单的Vue-Router就完成啦。