export const ErrorPageRoute = {
    path: '/:path(.*)*', 假设为A
    name: 'ErrorPage',
    component: HelloWorld,
    meta: {
        title: 'ErrorPage',
        hideBreadcrumb: true,
    },
    children: [
        {
            path: '/:path(.*)*', 假设为B
            component: HelloWorld,
            name: 'ErrorPage',
            meta: {
                title: 'ErrorPage',
                hideBreadcrumb: true,
            },
        },
    ],
};

如果父子路由路径和名称都一样,会导致父路由被删除

原因

  1. 路由A、B传入addRoute后,会调用matcher.addRoute
  2. 传入评分函数,给两个函数评分存在score里
  3. 防止重复路由会删除名为 ErrorPage 的路由
  4. 执行insertMatcher函数,首先会findInsertionIndex查找插入路由的下标
  5. 通过 const insertionAncestor = getInsertionAncestor(matcher);查找路由A的父路由
  6. 因为如下代码,路由A没有父路由,所以没有祖先,因此不会执行if (insertionAncestor) 内把子路由插在父路由前的代码

    function getInsertionAncestor(matcher) {
        let ancestor = matcher;
        while ((ancestor = ancestor.parent)) {
            if (isMatchable(ancestor) &&
                comparePathParserScore(matcher, ancestor) === 0) {
                return ancestor;
            }
        }
        return;
    }
  7. 下面路由A此时已经被插入进路由中,再次执行matcher.addRoute插入路由B
  8. 传入评分函数评分,此时,因为AB路由path相同,评分也会相同
  9. 防止重复路由会删除名为 ErrorPage 的路由,注意,此时会导致名称相同的路由A被删除
  10. 执行insertMatcher函数,首先会findInsertionIndex查找插入路由的下标
  11. 执行到const insertionAncestor = getInsertionAncestor(matcher);

    function getInsertionAncestor(matcher) {
        let ancestor = matcher; // 复制B给ancestor
        while ((ancestor = ancestor.parent)) { // 获取B的父路由,即A
            if (isMatchable(ancestor) && // 确定路由是否是可访问对象
                comparePathParserScore(matcher, ancestor) === 0) { // 比较path评分,前面说过,因为path相同所以评分相同,因此比较答案是0
                return ancestor; // 返回父路由A对象
            }
        }
        return;
    }
  12. insertionAncestor为A,执行如下代码。(upper通过二分法评分来确定适合插入的位置,在这里为整个路由的length)

    const insertionAncestor = getInsertionAncestor(matcher);
    if (insertionAncestor) {
        upper = matchers.lastIndexOf(insertionAncestor, upper - 1); // 查找父路由的下标,因为查找不到所以upper是-1
        if ((process.env.NODE_ENV !== 'production') && upper < 0) {...} // 生产环境不会报错,upper小于0执行
    }
    return upper; // 注意这里,即使小于0一样会返回upper
  13. 执行 matchers.splice(index, 0, matcher); 插入路由,因为是-1所以插入位置是倒数第一个前面

    function insertMatcher(matcher) {
        const index = findInsertionIndex(matcher, matchers);
        matchers.splice(index, 0, matcher); // 执行此代码插入
        // only add the original record to the name map
        if (matcher.record.name && !isAliasRecord(matcher)) // 通常isAliasRecord为false,我也不知道什么时候为true。我说的是取反之前的执行结果
            matcherMap.set(matcher.record.name, matcher); // 将原始记录设置到映射表
    }

从上面的执行流程可以知道,为什么名称+路径都相同时才会导致新的路由会插入到倒数第二个位置。

名称不同

父路由不会被删除

路径不同

导致评分不同,getInsertionAncestor返回为空,无法直接插入到父路由前。会回返回upper当做插入位置