前面咱們講過,在使用 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
中,拿到匹配的路由對象,進行路由跳轉