前面咱们讲过,在使用 vue-router
的时候,主要有如下几个步骤:javascript
// 1. 安装 插件 Vue.use(VueRouter); // 2. 建立router对象 const router = new VueRouter({ routes // 路由列表 eg: [{ path: '/foo', component: Foo }] }); // 3. 挂载router const app = new Vue({ router }).$mount('#app');
而后再进行路由跳转的时候,咱们会有如下几种使用方式 。 详细使用请查看官方文档html
// 字符串 router.push('home'); // 对象 router.push({ path: 'home' }); // 命名的路由 router.push({ name: 'user', params: { userId: '123' } }); // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' } });
那么,你有没有想过, push
进去的对象是如何与咱们以前定义的 routes
相对应的 ??
接下来,咱们一步步来进行探个究竟吧!vue
以前咱们说过 push 方法的具体实现, 里面主要是经过 transitionTo 来实现路由匹配并切换java
// src/history/hash.js // 跳转到 push(location: RawLocation, onComplete ? : Function, onAbort ? : Function) { const { current: fromRoute } = this this.transitionTo(location, route => { pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) }
因此咱们来看看 transitionTogit
// src/history/base.js // 切换路由 transitionTo(location: RawLocation, onComplete ? : Function, onAbort ? : Function) { // 匹配路由 // 根据路径获取到匹配的路径 const route = this.router.match(location, this.current) // 跳转路由 this.confirmTransition(route, () => { // ...more }, err => { // ...more }) }
这里看到, transitionTo 主要处理两件事github
咱们来看看具体如何匹配路由的 , 这里直接调用了匹配器的 match 方法正则表达式
// 获取匹配的路由对象 match( raw: RawLocation, current ? : Route, redirectedFrom ? : Location ): Route { // 直接调用match方法 return this.matcher.match(raw, current, redirectedFrom) }
export default class VueRouter { constructor() { // ...more // 建立匹配器 this.matcher = createMatcher(options.routes || [], this); // ...more } }
在 VueRouter 实例化的时候, 会经过咱们以前设置的 routers , 以及 createMatcher 建立一个匹配器, 匹配器包含一个 match 方法,用于匹配路由vue-router
// 文件位置: src/create-matcher.js // 建立匹配 export function createMatcher( routes: Array<RouteConfig>, router: VueRouter ): Matcher { // 建立 路由映射的关系 ,返回对应的关系 const { pathList, pathMap, nameMap } = createRouteMap(routes); // 添加 路由 function addRoutes(routes) { createRouteMap(routes, pathList, pathMap, nameMap); } // 匹配规则 function match( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { // 路径 const location = normalizeLocation(raw, currentRoute, false, router); const { name } = location; // 若是存在 name if (name) { // 找出匹配的 const record = nameMap[name]; if (!record) return _createRoute(null, location); // ...more if (record) { location.path = fillParams( record.path, location.params, `named route "${name}"` ); return _createRoute(record, location, redirectedFrom); } } else if (location.path) { // 根据路径寻找匹配的路由 location.params = {}; for (let i = 0; i < pathList.length; i++) { const path = pathList[i]; const record = pathMap[path]; // 查找匹配的路由 if (matchRoute(record.regex, location.path, location.params)) { return _createRoute(record, location, redirectedFrom); } } } // no match return _createRoute(null, location); } // 建立路由 function _createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { // ...more return createRoute(record, location, redirectedFrom, router); } return { match, addRoutes }; }
export function createRouteMap( routes: Array<RouteConfig>, oldPathList?: Array<string>, oldPathMap?: Dictionary<RouteRecord>, oldNameMap?: Dictionary<RouteRecord> ): { pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord> } { // the path list is used to control path matching priority // 数组,包括全部的 path const pathList: Array<string> = oldPathList || []; // $flow-disable-line // 对象 , key 为 path , 值为 路由对象 const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null); // $flow-disable-line // 对象 , key 为 name , 值为 路由对象 const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null); // 循环遍历 routes ,添加路由记录 routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route); }); // ensure wildcard routes are always at the end // 确保 * 匹配符放到最后面 for (let i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]); l--; i--; } } return { pathList, pathMap, nameMap }; }
addRouteRecord 主要完成了几项工做segmentfault
// 添加路由记录对象 function addRouteRecord( pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord>, route: RouteConfig, parent?: RouteRecord, matchAs?: string ) { const { path, name } = route; // ... const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}; const normalizedPath = normalizePath( path, parent, pathToRegexpOptions.strict ); if (typeof route.caseSensitive === 'boolean') { pathToRegexpOptions.sensitive = route.caseSensitive; } // 路由记录对象 const record: RouteRecord = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, instances: {}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {}, props: route.props == null ? {} : route.components ? route.props : { default: route.props } }; // ... if (!pathMap[record.path]) { pathList.push(record.path); pathMap[record.path] = record; } if (name) { if (!nameMap[name]) { nameMap[name] = record; } // ... } }
// 文件位置: src/util/route.js // 建立路由对象 export function createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: ?Location, router?: VueRouter ): Route { const stringifyQuery = router && router.options.stringifyQuery; // 请求参数 let query: any = location.query || {}; try { query = clone(query); } catch (e) {} // 生成路由对象 const route: Route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || '/', hash: location.hash || '', query, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : [] }; if (redirectedFrom) { route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery); } // 冻结路由对象,防止篡改 return Object.freeze(route); }
createRoute 生成的对象,即是是咱们常常用到的路由对象。 当前激活的路由信息对象则是
this.$route
路由是否匹配 , 主要是经过 path-to-regexp , 来建立一个正则表达式 , 而后 , 经过这个正则来检查是否匹配数组
import Regexp from 'path-to-regexp'; // ...more // 编译路径,返回一个正则 function compileRouteRegex( path: string, pathToRegexpOptions: PathToRegexpOptions ): RouteRegExp { const regex = Regexp(path, [], pathToRegexpOptions); // ...more return regex; }
关于 path-to-regexp ,这里主要讲几个例子。
import Regexp from 'path-to-regexp'; // 假如咱们页面 path 为 /about let reg = Regexp('/about', [], {}); // reg ==> /^\/about(?:\/(?=$))?$/i '/about'.match(reg); // ["/about", index: 0, input: "/about", groups: undefined] '/home'.match(reg); // null // 假如咱们页面 path 为 /about/:id let reg = Regexp('/about/:id', [], {}); // reg ==> /^\/about\/((?:[^\/]+?))(?:\/(?=$))?$/i '/about'.match(reg); // null '/about/123'.match(reg); //["/about/123", "123", index: 0, input: "/about/123", groups: undefined]
具体文档可参照这里 : path-to-regexp
最后经过正则检查路由是否匹配, 匹配结果非 null 则表示路由符合预先设定的规则
// 匹配路由规则 function matchRoute(regex: RouteRegExp, path: string, params: Object): boolean { const m = path.match(regex); if (!m) { return false; } else if (!params) { // 没参数直接返回true return true; } // ...more, 这里对参数作了一些处理 return true; }
最后,对路由匹配作一个总结 。 路由匹配具体的步骤有:
match
方法push
的时候,调用到 match
方法match
方法里面,从路由的映射关系里面,经过编译好的正则来断定是否匹配,返回最终匹配的路由对象transitionTo
中,拿到匹配的路由对象,进行路由跳转