本帖最后由 大蓝鲸小蟀锅 于 2020-3-26 22:48 编辑
大名鼎鼎的Vue Router是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们,使用步骤特别清晰。
使用步骤分成template部分和js部分
template部分:
1、使用router-link组件来导航,通过传入to属性指定链接(默认会被渲染成一个<a>标签)
2、使用router-view组件来占位将来路由匹配到的组件,会被渲染到这里
js部分:
1、导入Vue和vue-router(命名成VueRouter)、要调用Vue.use(VueRouter)
2、定义路由表,每一个路由至少包含path和component两个属性,前者表示将来浏览器要被匹配到的路径,后者表示正常匹配到该path以后要渲染的组件(当然,还可以指定路由模式为hash或者history,不指定的话默认为hash)
3、创建一个路由VueRouter实例,将先前创建好的路由表置于该实例当中
4、将第3步创建好的路由实例挂载到根实例上,属性为router,一般取路由实例的名称也叫router,因为挂载的时候可以使用es6中对象的简写形式(key和value同名时,可只写其中一个)
以上便是使用vue-router的使用步骤,当然还有很多其他功能,比如:动态路由匹配、嵌套路由、编程式导航、命名路由等其他特性
具体可移步至官方文档Vue Router
下面我们尝试着自己实现一个简易的vue-router:
分析:官方的vue-router提供了哪些重要东西?
比如:有两种路由模式可供选择(hash和history)、提供了两个全局组件router-link、router-view、每一个组件中都可以使用this.$router和this.$route这两个属性、并且最重要的是使用的时候需要调用Vue的use方法(查阅文档得知,是默认需要调用vue-router的install方法)
[JavaScript] 纯文本查看 复制代码 class VueRouter {
constructor({ mode, routes }) {
this.mode = mode || 'hash'
this.routes = routes || []
// 格式化路由表 [] => {}
// [{path: '/home/, component: Home},{path: }] => {'/home': Home, '/about': About}
this.routesMap = this.createRoutesMap(this.routes)
// 需要记录一下当前路由状态
this.history = { current: null }
this.init()
}
init() {
// 判断模式
if (this.mode === 'hash') {
location.hash ? '' : location.hash = '#/'
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1)
})
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1)
console.log('this.history', this.history)
})
} else {
location.pathname ? '' : location.pathname = '/'
window.addEventListener('load', () => {
console.log('load')
this.history.current = location.pathname
})
window.addEventListener('popstate', () => {
this.history.current = location.pathname
})
}
}
// 格式化路由表
createRoutesMap(routes) {
return routes.reduce((prev, current) => {
prev[current.path] = current.component
return prev
}, {})
}
}
// 使用use就会调用install方法
VueRouter.install = function (Vue) {
console.log('install')
// 每个组件都router得有 this.$router this.$route
// 在所有组件中如何拿到同一个路由的实例
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
// 根组件
this._root = this // 缓存一下当前实例
this._router = this.$options.router // 获取当前路由实例
Vue.util.defineReactive(this, 'abc', this._router.history)
} else {
// 父 -> 子 -> 孙
this._root = this.$parent._root
}
Object.defineProperty(this, '$router', { // VueRouter的实例
get() {
return this._root._router
}
})
Object.defineProperty(this, '$route', {
get() {
// 返回当前页面的状态
return {
current: this._root._router.history.current
}
}
})
}
})
Vue.component('router-link', {
props: {
to: {
type: String,
value: '/'
},
// tag: {
// String
// }
},
render(h) {
let mode = this._self._root._router.mode
// console.log(this._self._root)
// let tag = this.tag
return (
<a href={mode === 'hash' ? `#${this.to}` : this.to}>
{
this.$slots.default
}
</a>
)
}
})
Vue.component('router-view', {
render(h) {
let current = this._self._root._router.history.current
let routesMap = this._self._root._router.routesMap
return h(routesMap[current])
}
})
}
export default VueRouter
代码分析:
1、既然能通过new的形式实例化一个实例,咱们就可以使用类的形式创建一个VueRouter类(当然,构造函数的形式也是可以的)、根据编写插件的要求必须要提供一个install方法,可以直接在类上定义一个install方法,该方法调用时会将Vue当做参数传入;倘若你想传多个参数,那也是可以的
2、用户使用时可能会传递mode和routes等属性(暂时考虑这两个)用来指明当前路由模式和路由规则(默认是hash模式),VueRouter类里面可以通过构造函数constructor接受mode和routes,以上代码是通过对参数解构,直接拿到mode和routes,而后直接将这两个属性置于当前类的私有属性上
3、对用户传入的路由规则进行格式化,格式化成一个对象的形式 -> { '/home': Home, '/about': About } ,方便后面直接根据浏览器上的路径匹配到当前组件,不至于后面还要遍历routes数组取组件更麻烦一些
4、初始化当前路由的状态(当前浏览器的访问路径是谁,就保存谁)
5、开始对页面一加载时进行判断,如果是hash模式时判断当前浏览器路径是否带有#/,如果没有则自行加上;如果是history时判断当前浏览器路径是否带有/,如果没有也默认加上/;并且是hash模式时添加了hashchange事件,是history时,添加了popstate事件,都是用来监听用户操作的行为,并且更新当前路由的状态,也就是第四步初始了的状态。
6、install方法里要做一些混合操作,因为每个组件里面都有$router和$route这两个属性,可以看出是定义在全局上(当前this上的),在beforeCreate方法里通过Object.defineProperty方法在this上添加$router和$route这两个属性。
7、在混入的beforeCreate方法里可以通过this.$options.router判断当前组件是根实例(因为只有根实例上才会注册router属性),而后,缓存一下当前实例到_root上,缓存当前路由实例到_router上,后面其他组件可以通过$parent._root获取到当前路由实例;综上,在定义$router的get方法里可以返回this._root._router(这就是当前的VueRouter实例,上面包含一些push、go、back方法等),在定义$route的get方法里可以返回当前实例上的当前状态等属性(通过this._root_router.history.current)
8、两个全局组件,可以通过Vue.component的形式创建,同时可以使用render函数返回组件的UI。其中,router-view中的render需要先获取当前路由状态和格式化后的路由表对象,最后通过h(createElement函数)渲染当前状态所对应的组件;router-link就比较简单了,直接返回一个a标签,其中要根据props接受用户传递过来的to属性,根据路由模式判断href中的属性是否带#,并且通过this.$slot.default获取到标签内的文本内容,这里面的写法很类似react,需要添加一对大括号来嵌入js表达式
9、第56行代码,需要通过Vue.util.defineReactive为当前路由的history添加响应式,因为router-view中需要获取到当前路由状态和当前格式化后的路由对象。因为,根据事件循环机制,load监听事件是慢于install方法的,避免先加载的地方取不到后加载的属性而报错问题。
10、使用es6Module规范,默认导出当前VueRouter即可
以上便是自己实现一个VueRouter的大致代码和代码分析,有兴趣的同学可以把代码拿到本地跑一下,切身体验一下。
其中需要本身具备的知识点有:两种路由模式实现的原理、es6 api的熟练掌握、vue里面高级api的掌握(混合和全局定义组件render方法的编写)
|