这是对vue-router 3 版本的源码分析。
本次分析会按以下方法进行:
- 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
- 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
- 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。
本章讲解router中嵌套是如何实现的。
另外我的vuex3源码分析也发布完了,欢迎大家学习:
vuex3 最全面最透彻的源码分析
还有vue-router的源码分析:
vue-router 源码分析——1. 路由匹配
vue-router 源码分析——2. router-link 组件是如何实现导航的
vue-router 源码分析——3. 动态路由匹配
vue-router 源码分析——4.嵌套路由
官方例子
// 创建的 app
<div id="app"><router-view></router-view>
</div>const User = {template: `<div class="user"><h2>User {{ $route.params.id }}</h2><router-view></router-view></div>`
}const router = new VueRouter({routes: [{path: '/user/:id',component: User,children: [{// 当 /user/:id/profile 匹配成功,// UserProfile 会被渲染在 User 的 <router-view> 中path: 'profile',component: UserProfile},{// 当 /user/:id/posts 匹配成功// UserPosts 会被渲染在 User 的 <router-view> 中path: 'posts',component: UserPosts}]}]
})
路由记录:
- 在VueRouter初始化,生成路由记录 RouteRecord 时,子路由会记录对应的父路由到子路由的record.parent 属性上。
// create-route-map.js
function addRouteRecord(pathList: Array<string>,pathMap: Dictionary<RouteRecord>,nameMap: Dictionary<RouteRecord>,route: RouteConfig,parent?: RouteRecord,matchAs?: string
) {const { path, name } = routeconst normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)const record: RouteRecord = {path: normalizedPath,components: route.components || { default: route.component},parent,...}if (route.children) {...route.children.forEach(child => {// 递归调用 addRouteRecord,将父record加入到子record的parent中addRouteRecord(pathList, pathMap, nameMap, child, record, undefined)}) }
}
- 在触发路由匹配时,匹配完路由记录且创建路由时,会按 [父record,子reocrd…] 的顺序结构创建一个matched对象在创建的路由对象 Route 上。
// ./util/route.js
export function createRoute(record: ?RouteRecord,location: Location,redirectedFrom?: ?Location,router?: VueRouter
): Route {const route: Route = {path: location.path || '/',params: location.params || {},matched: record ? formatMatch(record) : [],... }...return Object.freeze(route)
}function formatMatch(record: ?RouteRecord): Array<RouteRecord> {const res = []while (record) {res.unshift(record)record = record.parent }return res
}
- 在渲染路由时,从根节点的开始依次渲染,通过depth来记录当前的嵌套深度,按照当前深度取出matched中对应的渲染数据。
// ./components/view.js
export default {...render(_, { props, children, parent, data }) {data.routerView = trueconst h = parent.$createElementconst route = parent.$routelet depth = 0while (parent && parent._routerRoot !== parent) {const vnodeData = parent.$vnode ? parent.$vnode.data : {}// 如果父节点也是router-view,则深度+1if (vnodeData.routerView) {depth++}parent = parent.$parent}data.routerViewDepth = depth// 取出对应深度的 matched和component 作为当前url对应路由的渲染数据const matched = route.matched[depth]const component = matched && matched.components[name]if (!matched || !component) {return h() }...return h(component, data, children)}
}
- 这样就完成了嵌套路由的代码设计和匹配实现。