vue-router 源码分析——8.重定向

这是对vue-router 3 版本的源码分析。
本次分析会按以下方法进行:

  1. 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
  2. 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
  3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。

本章讲解router中重定向是如何实现的。
另外我的vuex3源码分析也发布完了,欢迎大家学习:
vuex3 最全面最透彻的源码分析
还有vue-router的源码分析:
vue-router 源码分析——1. 路由匹配
vue-router 源码分析——2. router-link 组件是如何实现导航的
vue-router 源码分析——3. 动态路由匹配
vue-router 源码分析——4.嵌套路由
vue-router 源码分析——5.编程式导航
vue-router 源码分析——6.命名路由
vue-router 源码分析——7.命名视图

官方示例:

  • 重定向是通过 routes 配置来完成,重定向的目标也可以是一个命名路由,或者一个方法。
const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' },
    { path: '/b', component: b_component} // 重定向的路由必须在router中定义
  ]
})

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

Router初始化:

  • Router初始化生成匹配器和路由记录表,redirect属性会被复制到路由的路由记录上:
// router.js
import type { Matcher } from './create-matcher'
export default class VueRouter {
    ...
    constructor(options: RouterOptions = {}) {
        ...
        this.matcher = createMatcher(options.routes || [], this)
    }
}

// ./create-matcher.js
import { createRouteMap } from './create-route-map'
export function createMatcher(
    routes: Array<RouteConfig>,
    router: VueRouter
): Matcher {
    const { pathList, pathMap, nameMap } = createRouteMap(routes)
    ...
}

// ./create-route-map/js
export function createRouteMap(
    routes: Array<RouteConfig>,
    ...
): {
    pathList: Array<string>,
    pathMap: Dictionary<RouteRecord>,
    nameMap: Dictionary<RouteRecord>
} {
    const pathList: Array<string> =  []
    const pathMap: Dictionary<RouteRecord> = Object.create(null)
    const nameMap: Dictionary<RouteRecord> = Object.create(null)
    routes.forEach(route => {
        addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
    })
    ...
}

function addRouteRecord(
    pathList: Array<string>,
    pathMap: Dictionary<RouteRecord>,
    nameMap: Dictionary<RouteRecord>,
    route: RouteConfig,
    parent?: RouteRecord,
    matchAs?: string
) {
    ...
    const record: RouteRecord = {
        redirect: route.redirect,
        ...    
    }
}

触发逻辑:

  • 当试图跳转到url ‘/a’ 时,会触发路由匹配。
  • 首先匹配到路由路径为’/a’的路由记录,然后判断它是否有redirect属性,如果有就执行redirect函数。
  • 接着在redirect函数中取出redirect内容,对其进行了结构类型统一化。这里就解释了为什么配置重定向的目标可以是字符串形式的path,或者命名路由,或者方法。
  • 最后,构建了一个基于redirect目标的数据,作为参数传入match函数进行路由匹配。这样的递归调用也能解决多重的重定向。
// create-matcher.js
export function createMatcher(
    routes: Array<RouteConfig>,
    router: VueRouter
): Matcher {
    const { pathList, pathMap, nameMap } = createRouteMap(routes)
    ...
    function match(
       raw: RawLocation,
        currentRoute?: Route,
        redirectedFrom?: Location     
    ): Route {
        const location = normalizeLocation(raw, currentRoute, false, router)
        const { name } = location
        if (name) {
            // 基于命名路由的匹配逻辑        
        } 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)) {
                      // 这里匹配到路由'/a'的相关记录后,执行创建路由的操作
                      return _createRoute(record, location, redirectedFrom)
                  }
              }      
        }
    }
    function _createRoute(
        record: ?RouteRecord,
        location: Location,
        redirectedFrom?: Location    
    ): Route {
        // 如果当前的路由记录有重定向,就执行重定向的相关逻辑,否则创建'/a'的路由内容
        if (record && record.redirect) {
            return redirect(record, redirectedFrom || location)        
        }
        ...
        return createRoute(record, location, redirectedFrom, router)
    }
    function redirect(
        record: RouteRecord,
        location: Location
    ): Route {
    	const originalRedirect = record.redirect
        // 下面这段代码解释了为什么重定向可以是一个方法,同时接受的参数是目标路由,
        let redirect = typeof originalRedirect === 'function'
            ? originalRedirect(createRoute(record, location, null, router))
            : originalRedirect
        // 最后无论重定向设置的是字符串,命名路由,方法,都会统一成一个对象结构
        if (typeof redirect === 'string') {
            redirect = {path = redirect}        
        }
        ...
        const re: Object = redirect
        const { name, path } = re
        ...
        if (name) {
            const targetRecord = nameMap[name]
            ...
            return match({
                name,
                ...            
            }, undefined, location)       
        } else if (path) {
        	const rawPath = resolveRecordPath(path, record) 
            const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
            return match({
                path: resolvedPath, // 官网示例中定义的重定向都不复杂,这里的path = '/b'没有任何改变
                ...          
            }, undefined, location)       
        }
    }
}

细节补充:

  • 当重定向完成,匹配路由成功后,调用createRoute函数生成匹配路由时,增加了一个redirectedFrom属性,记录的是它的重定向的源路径。这样可以方便用户查看或者执行其他功能。
// ./util/route.js
export function createRoute(
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: ?Location,
    router?: VueRouter
): Route {
    ...
    const route: Route = {
        ...  
    }
    if (redirectedFrom) {
        route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)            
    }
    return Object.freeze(route)
}
  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值