当前位置: 代码迷 >> 综合 >> vue-router3源码注解系列 /src/create-route-map.js
  详细解决方案

vue-router3源码注解系列 /src/create-route-map.js

热度:40   发布时间:2023-10-26 11:38:35.0
/* @flow *//*用于路径匹配的正则表达式对象。 */
import Regexp from 'path-to-regexp'
//用于清理 uri 上连续重复的 / 。
import {
     cleanPath } from './util/path'
//断言,警告。
import {
     assert, warn } from './util/warn'/*createRouteMap() 函数: 第一个参数 routes 就是 new VueRouter( { routes: [xxxx] } ) 中的 routes。 */
export function createRouteMap(routes: Array<RouteConfig>,oldPathList?: Array<string>,oldPathMap?: Dictionary<RouteRecord>,oldNameMap?: Dictionary<RouteRecord>,parentRoute?: 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//以 path 作为 key,存放所有的路由描述对象。const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)// $flow-disable-line//以 name 作为 key,存放所有路由描述对象。const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)//routes 是一个数组对象。也就是用户手写的 new VueRouter( { routes: [xxx] } ) 的 routes 配置数据//遍历routes数组的数据,将所有元素转化为 router record 对象。且会被记录到 pathMap, nameMap 对象中。routes.forEach((route) => {
    //pathList: 全局的 pathList,所有路由创建过程中是同一个 pathList.//pathMap: 全局的 pathMap, 所有路由创建过程中是同一个 pathMap.//nameMap: 全局的 nameMap, 所有路由创建过程中是同一个 nameMap.//route: 就是要转化为的 router record 对象的数据。//parentRoute:表示父 route 数据转化成的 router record 对象。addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)})/*** 这一段主要处理 pathList 中的 path == ‘*’ 的路径,且移到数组末尾。*///依次遍历 pathList。 pathList 中元素都是完整绝对路径。// 如果 pathList 中存在 path 路径为 “*”,则移除该元素,并且追加到 pathList 数组末尾。for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
    //先移除第 i 个元素,pathList.splice() 返回被移除元素的数组。//因为只移除了一个,则获取下标为0的元素,然后塞到数组的末尾。pathList.push(pathList.splice(i, 1)[0])//之所以要 l--, 是因为 l ~ length 的元素就是 * 号,已经处理过了。//如果 l 不 --, 存在多个 * 的情况,则会死循环。l--//此时i+1 ~ length-1 的元素已经迁移。后一个元素已经占在了 i 位置。// 所以需要 i--,然后下一轮循环就能取到下一个没有判断*的元素。i--}}/*如果是开发环境,且 pathList 中 path 元素,不是以 * 或者 / 开头,则报警告。*/if (process.env.NODE_ENV === 'development') {
    // warn if routes do not include leading slashesconst found = pathList// check for missing leading slash.filter((path) => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')if (found.length > 0) {
    const pathNames = found.map((path) => `- ${
      path}`).join('\n')warn(false,`Non-nested routes must include a leading slash character. Fix the following routes: \n${
      pathNames}`)}}/*返回 matcher 对象. {pathList, //记录所有 route 的完整的绝对路径 path。pathMap, //记录所有的 router record 对象,以 path 作为 key 进行存储。nameMap //记录所有的 router record 对象,以 name 作为 key 就行存储。}*/return {
    pathList,pathMap,nameMap,}
}/*addRouteRecord() 函数,用于添加 route 数据。pathList: 存储路由 path。pathMap: 以 path 为 key, 存储路由描述信息。nameMap: 以 name 为 key, 存储路由描述信息。route: 用户配置的当个路由数据信息。 */
function addRouteRecord(pathList: Array<string>,pathMap: Dictionary<RouteRecord>,nameMap: Dictionary<RouteRecord>,route: RouteConfig,parent?: RouteRecord,matchAs?: string
) {
    /*route 数据为:{name: xxx,path: xxx,component: xxx,children: [ {name: xxx,path: xxx,component: xxx,}]meta: xxx}*///获取配置信息中的 path,name 属性。const {
     path, name } = route//非开发模式下if (process.env.NODE_ENV !== 'production') {
    //如果 path 没有指定,则报警告提示。assert(path != null, `"path" is required in a route configuration.`)assert(//如果 route.component 不是字符串类型。 todotypeof route.component !== 'string',`route config "component" for path: ${
      String(path || name)} cannot be a ` + `string id. Use an actual component instead.`)warn(// eslint-disable-next-line no-control-regex//判断path是不是只包含 128 个 ascii 码的字符。如果不是,则报警告。!/[^\u0000-\u007F]+/.test(path),`Route with path "${
      path}" contains unencoded characters, make sure ` +`your path is correctly encoded before passing it to the router. Use ` +`encodeURI to encode static segments of your path.`)}/*pathToRegexpOptions 内容为:{sensitive 大小写敏感 (default: false)strict 末尾斜杠是否精确匹配 (default: false)end 全局匹配 (default: true)start 从开始位置展开匹配 (default: true)delimiter 指定其他分隔符 (default: '/')endsWith 指定标准的结束字符whitelist 指定分隔符列表 (default: undefined, any character)}*///pathToRegexpOptions 表示编译正则的选项。//可以通过配置 route 的 pathToRegexpOptions 参数添加高级配选项。默认是空对象。const pathToRegexpOptions: PathToRegexpOptions =route.pathToRegexpOptions || {
    }//对路径名称进行归一化处理。//绝对路径直接返回;相对路径就拼接父 path。const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)//route.caseSensitive 属性如果存在,则设置到 pathToRegexpOptions 中。// caseSensitive: 表示大小写敏感。if (typeof route.caseSensitive === 'boolean') {
    //大小写敏感的正则表达式配置项。pathToRegexpOptions.sensitive = route.caseSensitive}const record: RouteRecord = {
    //normalizedPath: 完整的绝对路径。path: normalizedPath,//根据完整的路径,以及路径匹配配置参数,生成路径匹配正则对象。regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),//设置 component。//如果是别名路由的创建,则 component 为 undefined。components: route.components || {
     default: route.component },//设置路由别名。别名类似于重定向,但是显示的路径会是别名的路径。//别名可以设置多个,用数组表示;如果只有一个且是字符串,则归一化为数组。alias: route.alias? typeof route.alias === 'string'? [route.alias]: route.alias: [],instances: {
    },enteredCbs: {
    },//路由名字name,//父路由 record 对象。parent,//如果是 root route, 则 matchAS 为 undefined.matchAs,//记录路由的 redirect 重定向属性。redirect: route.redirect,//记录路由独享的守卫/* {path: "/users/:id",component: xxx,beforeEnter: ( to, from ) => { return false } } */beforeEnter: route.beforeEnter,//记录 route 元数据。一般用于配置keepalive, required 等。meta: route.meta || {
    },//props:// 如果没配置有 route.props,则默认为空对象。// 如果配置有 route.props, 则如果 component 存在,则记录 route.props 数据。// 说明: props 类似于 query, params,都是用于携带路由传参的。不过 props 会自动把数据传递到组件的 props 中。route.props == null? {
    }: route.components? route.props: {
     default: route.props },}//如果存在子路由数据。if (route.children) {
    // Warn if route is named, does not redirect and has a default child route.// If users navigate to this route by name, the default child will// not be rendered (GH Issue #629)if (process.env.NODE_ENV !== 'production') {
    if (//如果路由 A 有名称,且没有配置重定向属性 redirect。如果子路由 B 配置的路径为 “/”,// 则此时如果使用命名路由的方式进行跳转到 A 路由,此时子路由 B 不会被渲染。route.name &&!route.redirect &&route.children.some((child) => /^\/?$/.test(child.path))) {
    warn(false,`Named Route '${
      route.name}' has a default child route. ` +`When navigating to this named route (:to="{name: '${
      route.name}'"), ` +`the default child route will not be rendered. Remove the name from ` +`this route and use the name of the default child route for named ` +`links instead.`)}}//先序遍历的形式依次将子 route 的数据以此生成 router record对象。route.children.forEach((child) => {
    //如果 route 是用户真实配置的 route 数据,则 matchAs 为 undefine。//如果 route 是 alias 生成的 route 数据,则 matchAs 为被别名的完整路径。// 子 route 会通过 matchAs 记录没有被别名的完整路径。const childMatchAs = matchAs? cleanPath(`${
      matchAs}/${
      child.path}`): undefined//pathList: 全局的 pathList,所有路由创建过程中是同一个 pathList.//pathMap: 全局的 pathMap, 所有路由创建过程中是同一个 pathMap.//nameMap: 全局的 nameMap, 所有路由创建过程中是同一个 nameMap.//child: 就是要转化的 route 数据。//record: 就是 parent。addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)})}//如果 record.path 没有被记录到 pathMap 中。//后面出现相同的 record.path,相当于直接丢弃。if (!pathMap[record.path]) {
    //将 record.path 收集到 pathList 中。 此时的 path 是完整的绝对路径。pathList.push(record.path)//将 router record 对象 以 record.path 作为 key,记录到 pathMap 中。pathMap[record.path] = record}/*如果配置了 route.alias 元素。(1) 将 router.alias 归一化为数组。(2) 过滤掉与 route.path 同名的 route.alias中的元素。(3) 将 route.alias 中元素, 封装成一个 route 数据。即 { path: router.alias[index], children },然后也被遍历生成 router record, 以及子 router record。*///如果配置了 route.alias 属性。if (route.alias !== undefined) {
    //如果 route.alias 不是对象(则是字符串),则归一化为数组。const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]//判断别名数组 aliases 存在与 router.path 相同,则打印警告信息。且将这条 alias 忽略。for (let i = 0; i < aliases.length; ++i) {
    const alias = aliases[i]if (process.env.NODE_ENV !== 'production' && alias === path) {
    warn(false,`Found an alias with the same value as the path: "${
      path}". You have to remove that alias. It will be ignored in development.`)// skip in dev to make it workcontinue}//以别名作为 path,生成对应的 route record 对象。const aliasRoute = {
    path: alias,children: route.children,}addRouteRecord(//全局的 pathList,所有路由创建过程中是同一个 pathList.pathList,//全局的 pathMap, 所有路由创建过程中是同一个 pathMap.pathMap,//全局的 nameMap, 所有路由创建过程中是同一个 nameMap.nameMap,//将别名封装成一个 route 数据。 { path: alias, children: [xxxx] }aliasRoute,//parent 是一个将 route 数据转换为 route record 对象。parent,//对于别名的 route record,matchAs 就是 record.path;//record.path 就是被别名的 path 的完整路径。record.path || '/' // matchAs)}}/*** 1、主要是判断 route.name 是否存在,如果存在,则作为 key,value 为 router record, 记录到 nameMap 中。* 2、如果 name 已经被记录到 nameMap 中,则直接忽略当前 route 数据。* 3、如果 name 还没有被记录到 nameMap 中,则进行记录。*///如果 route.name 存在if (name) {
    //如果不存在重复的 name,则在 nameMap 中以 name 作为 key,存储 router record。if (!nameMap[name]) {
    nameMap[name] = record//如果是重复的 name,且不是生产环境,且 matchAs 不为空,则报警告信息。} else if (process.env.NODE_ENV !== 'production' && !matchAs) {
    warn(false,`Duplicate named routes definition: ` +`{ name: "${
      name}", path: "${
      record.path}" }`)}}
}/*编译路由正则表达式。 */
function compileRouteRegex(//此时的 path 是完整的绝对路径。path: string,//正则匹配高级配置选项。pathToRegexpOptions: PathToRegexpOptions
): RouteRegExp {
    //Regexp 是 path-to-regexp 对象。 vue-router 使用 path-to-regexp.js 来进行路由规则匹配。//生成指定 path 的路由匹配正则对象。const regex = Regexp(path, [], pathToRegexpOptions)//单纯的判断是否有重复的 key.name 存在,如果有,则在非生产环境下报告警告信息。//没有实质性处理。if (process.env.NODE_ENV !== 'production') {
    //创建一个 keys 对象。const keys: any = Object.create(null)//如果 key.name 已经存在于 keys 中,则非生产环境会报警告信息。//这里仅仅是判断是否有重复的,有就给个警告提示,不做其他。regex.keys.forEach((key) => {
    warn(!keys[key.name],`Duplicate param keys in route with path: "${
      path}"`)keys[key.name] = true})}//返回正则匹配对象。 作为 router record 对象的 regex 属性。return regex
}/*对路径进行归一化处理。1、route: 数据中配置的 path 路径。2、parent: 表示是父 route。 { path: "parentPath", children: [{ path: "path" }] }3、strict: 末尾斜杠是否精确匹配 (default: false) */
function normalizePath(path: string,parent?: RouteRecord,strict?: boolean
): string {
    //如果 strict 为 false,则去除 path 末尾的 /。if (!strict) path = path.replace(/\/$/, '')//path[0] 表示获取第一个字符的子串。//如果 path 是以 "/" 开头,则表示是绝对路径,不需要拼接父路径。if (path[0] === '/') return path//如果 parent 不存在,则没有父路径可以拼接。if (parent == null) return path//用父 route 的 path 进行拼接。//cleanPath 用于处理多个 “/” 连续的情形。 比如 /user/name//age => /user/name/agereturn cleanPath(`${
      parent.path}/${
      path}`)
}
  相关解决方案