从添加router插件开始,在执行了vue add router
命令后,项目目录中会增加一个router目录并在main.js中导入router选项。
先从router目录下的index.js开始:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// 注册VueRouter插件
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')
}
]
const router = new VueRouter({
routes
})
// 导出router实例
export default router
从router的index.js文件可以看出,这里做了三件事:
在main.js文件中,vue-rotuer的修改:
表面上Vue-Router就做了这些事情,但在我们平常开发时,我们可以在每个组件中访问$router
,而在表面上我们并没有发现Vue-Router在main.js中将$router
挂载到Vue.prototype
上,那说明**$router
是在Vue-Router内部挂载**的。
还有一个就是我们平时在任意组件中都可以使用
和
组件,而这并不是Vue自身的组件,说明Vue-Router还声明了两个全局组件。
所以这里可以转换成Vue-Router实现的起步需求就是实现一个Vue插件,该插件起步的功能有:
$rotuer
router-link
和router-view
先创建一个vue-router.js文件,在文件中创建一个VueRouter类
$router
挂载$router
我们很容易想到在install方法去挂载,但这里有个问题,就是在执行install方法时,Vue还未实例化,这里并不能拿到router实例,Vue-Router的解析方法是在执行install方法时全局混入beforeCreate
生命周期方法,等这个生命周期方法执行时,Vue是已经实例化了,就可以拿到router实例了。
新建vue-router.js
class VueRouter {
}
// Vue插件需要声明一个install方法
VueRouter.install = function (Vue) {
// 全局混入beforeCreate生命周期方法
Vue.mixin({
beforeCreate() {
// 只要在Vue根实例中挂载,利用只有根实例选项上有router属性找到根实例
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
}
首先,我们要清楚在hash模式下,
和
组件到底有什么用。
<router-link to="/about">Aboutrouter-link>
<router-view>router-view>
组件接收一个to
属性,并将标签中内容渲染,从浏览器中可以看出其实就是一个a标签,to
属性改变的是a标签的href
属性。
组件的功能是在路由发生改变时,将对应路由的组件渲染到其中。
组件新建link.js
// 导出router-link组件配置对象
export default {
name: 'router-link',
props: {
to: {
type: String,
default: ''
}
},
render (h) {
// 利用渲染函数,生成一个a标签,href属性值如 /#/about,将默认插槽作为子元素
return h('a', {
attrs: {
href: '#' + this.to}}, this.$slots.default)
}
}
组件新建view.js
// 导出router-view组件配置对象
export default {
name: 'router-view',
render (h) {
// component存放当前路由对应的组件
let component = null
return h(component)
}
}
因为这是是用hash值的改变来实现地址修改,但其实页面并不会刷新,这里就需要手动去触发渲染
vue-router.js完善
import Link from './link'
import View from './view'
let Vue = null
class VueRouter {
constructor (options) {
// 保存路由选项
this.$options = options
// 声明一个响应式属性,存放当前路由路径
Vue.util.defineReactive(this, 'currentPath', '/')
// 监听初始化和hash值变化时,改变当前路由路径
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
// 创建路由map,保存路由表
this.routeMap = {
}
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route
})
}
onHashChange () {
// 这里只是先简单将 # 后的路径赋值给currentPath
this.currentPath = window.location.hash.slice(1)
}
}
// Vue插件需要声明一个install方法
VueRouter.install = function (_Vue) {
// 保存Vue引用
Vue = _Vue
// 全局混入beforeCreate生命周期方法
Vue.mixin({
beforeCreate() {
// 只要在Vue根实例中挂载,利用只有根实例选项上有router属性找到根实例
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 注册全局组件
Vue.component('router-link', Link)
Vue.component('router-view', View)
}
完善view.js
// 导出router-view组件配置对象
export default {
name: 'router-view',
render (h) {
// component存放当前路由对应的组件
let component = null
// 因为currentPath是响应式数据,在当前组件的render方法中使用该属性后,当currentPath改变,render会重新执行
// 根据$router上的currentPath的变化,动态匹配组件
const {
routeMap, currentPath} = this.$router
component = routeMap[currentPath] ? routeMap[currentPath].component : null
return h(component)
}
}
嵌套路由在路由配置时多了children
数组,在父路由组件中需要添加
组件。
实现嵌套路由的原理就是:
组件的深度得到depth,即可在matched中匹配到对应的路由vue-router.js修改
import Link from './link'
import View from './view'
let Vue = null
class VueRouter {
constructor (options) {
// 保存路由选项
this.$options = options
// 声明一个响应式数据,存放当前路由路径
// Vue.util.defineReactive(this, 'currentPath', '/')
// 将matched声明为一个响应式数据,存放当前匹配的路由数组
Vue.util.defineReactive(this, 'matched', [])
// 监听初始化和hash值变化时,改变当前路由路径
window.addEventListener('hashchange', this.onHashChange.bind(this))
window.addEventListener('load', this.onHashChange.bind(this))
// 创建路由map,保存路由表
// this.routeMap = {}
// this.$options.routes.forEach(route => {
// this.routeMap[route.path] = route
// })
}
onHashChange () {
// 这里只是先简单将 # 后的路径赋值给currentPath
// this.currentPath = window.location.hash.slice(1)
// 获取当前hash路径
this.current = window.location.hash.slice(1)
this.matched = []
// 匹配hash路径对应的路由
this.match()
}
match (routes) {
routes = routes || this.$options.routes
// 遍历路由选项
for (let route of routes) {
if (route.path === '/' && this.current === '/') {
this.matched.push(route)
return
}
// 匹配嵌套路由的情况
if (route.path !== '/' && this.current.indexOf(route.path) !== -1) {
this.matched.push(route)
if (route.children) {
this.match(route.children)
}
return
}
}
}
}
// Vue插件需要声明一个install方法
VueRouter.install = function (_Vue) {
// 保存Vue引用
Vue = _Vue
// 全局混入beforeCreate生命周期方法
Vue.mixin({
beforeCreate() {
// 只要在Vue根实例中挂载,利用只有根实例选项上有router属性找到根实例
if (this.$options.router) {
Vue.prototype.$router = this.$options.router
}
}
})
// 注册全局组件
Vue.component('router-link', Link)
Vue.component('router-view', View)
}
view.js修改
// 导出router-view组件配置对象
export default {
name: 'router-view',
render (h) {
// 标记当前组件为routerView
this.$vnode.data.routerView = true
// component存放当前路由对应的组件
let component = null
// 当前router-view组件的深度
let depth = 0
let parent = this.$parent
// 向上遍历是否还存在router-view组件,得到当前router-view的深度
while(parent) {
if (parent.$vnode.data && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
// 取出匹配路由的组件
const matched = this.$router.matched[depth]
component = matched && matched.component
// 因为currentPath是响应式数据,在当前组件的render方法中使用该属性后,当currentPath改变,render会重新执行
// 根据$router上的currentPath的变化,动态匹配组件
// const {routeMap, currentPath} = this.$router
// component = routeMap[currentPath] ? routeMap[currentPath].component : null
return h(component)
}
}
这里的实现并没有像官方一样使用函数式组件