VueRouter使用
创建router
Vue.use使用VueRouter插件,默认调用VueRouter的install方法
创建VueRouter实例,传入参数options
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/' ,
name: 'Home' ,
component: Home
},
{
path: '/about' ,
name: 'About' ,
component: About,
children: [
{
path: 'a' ,
component: {
render(h){ return <h1>this is about/a</h1>}
},
},
{
path: 'b' ,
component: {
render(h){ return <h1>this is about/b</h1>}
},
}
]
}
]
})
注入
new Vue({
router,
render: h => h(App)
}).$mount ('#app' )
install
当执行Vue.use时,默认执行插件的install方法
import RouterView from './components/router-view.js'
import RouterLink from './components/router-link.js'
const install = (Vue) => {
Vue.mixin({
beforeCreate () {
if (this.$options .router) {
this._routerRoot = this;
this._router = this.$options .router
this._router.init(this);
Vue.util.defineReactive(this, '_route' , this._router.history.current)
} else {
this._routerRoot = this.$parent && this.$parent ._routerRoot;
}
}
})
Object.defineProperty(Vue.prototype, '$route' , {
get () {
return this._routerRoot._route
}
})
Object.defineProperty(Vue.prototype, '$router' , {
get () {
return this._routerRoot._router
}
})
Vue.component('RouterView' , RouterView)
Vue.component('RouterLink' , RouterLink)
}
export default install
整体架构图
VueRouter构造函数
初始化时,创建匹配器matcher和history实例
init:获取当前location,找到匹配的record;监听hash变化;
match:根据location获取对应的record
push:进行跳转
beforeEach:订阅路由钩子
import install from './install.js'
import HashHistory from './history/hash.js'
import createMatcher from './create-matcher.js'
class VueRouter {
constructor(options) {
this.options = options;
this.matcher = createMatcher(options.routes || [])
this.history = new HashHistory(this)
this.beforeEachs = []
}
match(location) {
return this.matcher.match(location)
}
push(location) {
this.history.transitionTo(location, () => {
window.location.hash = location
});
}
init (app) {
const history = this.history;
const setupHashListener = () => {
history.setupListener();
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener
)
history.listen((route) => {
app._route = route
})
}
beforeEach(cb) {
this.beforeEachs.push(cb);
}
}
VueRouter.install = install;
export default VueRouter
createMatcher
addRoutes:routes格式化后合并到pathList,pathMap中;
match:根据location在pathMap中找到对应的record,将当前record树中父子关联的record放在matched数组中,返回matched+path
import createRouteMap from './create-route-map.js' ;
import {createRoute} from './history/base.js'
export default function createMatcher(routes) {
let {pathList, pathMap} = createRouteMap(routes)
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap)
}
function match(location) {
let record = pathMap[location]
return createRoute(record, {
path: location
})
}
return {
addRoutes,
match
}
}
createRouteMap
遍历route,格式化后保存在pathList和pathMap上
export default function createRouteMap(routes, oldPathList, oldPathMap) {
let pathList = oldPathList || [];
let pathMap = oldPathMap || Object.create(null);
routes.forEach(route => {
addRouteRecord(route, pathList, pathMap)
})
return {
pathList,
pathMap
}
}
function addRouteRecord(route, pathList, pathMap, parent) {
let path = parent ? parent.path + '/' + route.path : route.path;
let record = {
path,
component: route.component,
parent,
}
if (!pathMap[path]) {
pathList.push(path);
pathMap[path] = record;
}
if (route.children) {
route.children.forEach(item => {
addRouteRecord(item, pathList, pathMap, record)
})
}
}
router组件
router-link
export default {
props: {
to: {
type : String,
required: true
},
tag: {
type : String,
default: 'a'
}
},
methods:{
handler () {
this.$router .push(this.to)
}
},
render(h) {
let tag = this.tag;
return <tag onClick={this.handler}>{this.$slots .default}</tag>
}
}
router-view
递归渲染:自上而下,父级得到匹配的record渲染后,depth+1
export default {
functional: true ,
render(h, {parent, data}){
let route = parent.$route ;
let depth = 0;
while (parent) {
if (parent.$vnode && parent.$vnode .data.routerView) {
depth++;
}
parent = parent.$parent
}
data.routerView = true
let record = route.matched[depth];
if (!record) {
return h();
}
return h(record.component, data)
}
}
history构造函数
base构造函数
createRoute:根据record树,将父子关系中的record都放在matched数组中
runQuene:按照iterator方式遍历执行路由钩子;因为可能存在异步,通过step函数实现
setupListener:监听hash变化,执行transitionTo
transitionTo:根据路径找到对应record,如果路径改变,执行路由钩子;路由钩子执行完后,会执行updateRoute
updateRoute:路径变化时,更新app._route;执行回调;将路径匹配的record绑定在current上,通过RouterView进行渲染
listen:订阅函数fn
export function createRoute(record, location) {
let res = [];
if (record){
while (record) {
res.unshift(record)
record = record.parent
}
}
return {
...location,
matched: res
}
}
function runQuene(quene, iterator, callback){
function step(index) {
if (index == quene.length) return callback()
let hook = quene[index];
iterator(hook, () => step(index+1))
}
step(0)
}
class History{
constructor(router) {
this.router = router;
this.current = createRoute(null, {
path: '/'
})
this.cb = undefined
}
transitionTo(location, callback){
let r = this.router.match(location);
if (location == this.current.path && r.matched.length == this.current.matched.length) {
return
}
let quene = this.router.beforeEachs
const iterator = (hook, next) => {
hook(this.current, r, next);
}
runQuene(quene, iterator, () => {
this.updateRoute(r, callback)
})
}
updateRoute(r, callback) {
this.current = r;
this.cb && this.cb(r);
callback && callback();
}
setupListener () {
window.addEventListener('hashchange' , () => {
this.transitionTo(window.location.hash.slice(1))
})
}
listen(cb) {
this.cb = cb
}
}
export default History
hash构造函数
初始化时,调用ensureSlash保证页面加载后就有hash
getCurrentLocation:获取当前的路径
import History from './base'
function ensureSlash () {
if (window.location.hash) {
return
}
window.location.hash = '/'
}
class HashHistory extends History {
constructor(router) {
super(router)
this.router = router
ensureSlash();
}
getCurrentLocation () {
return window.location.hash.slice(1)
}
}
export default HashHistory
hash改变视图刷新的原理