vue-router 源码分析——7.命名视图

这是对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.命名路由

官方定义

  • 有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。特意强调确保正确使用 components 配置 (带上 s)
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})
  • 注意上面使用了命名路由时,routes中使用了components做key,而不是component。同时结构变成了一个对象,存储的内容就是上面代码中的components对象。

命名视图初始化

  • 这里对应了router在初始化并创建匹配器时,对record的component的不同处理:
// create-route-map.js

function addRouteRecord(
    pathList: Array<string>,
    pathMap: Dictionary<RouteRecord>,
    nameMap: Dictionary<RouteRecord>,
    route: RouteConfig,
    parent?: RouteRecord,
    matchAs?: string
) {
    const { path, name } = route
    ...
    const record: RouteRecord = {
        components: route.components || {default: route.component},
        ...
    }
    ...
}
  • 然后,在使用router-view组件时,传入了一个name。如果对vue开发非常熟练的,大概已经知道router是如果定位到需要的component了:
    • 在view.js组件中,用props接受父组件传入的name,同时给他一个默认值default
    • 在匹配到对应路由的record后,对应的component即为recoed.components[name]
    • 这样就实现了基本的命名视图路由匹配逻辑。
    • 同时没有匹配的record或者component,则渲染一个空节点,这个也适合后面的嵌套路由逻辑。
// ./components/view.js
...
export default {
    ...
    props: {
        name: {
            type: String,
            default: 'default'        
        }    
    },
    render(...) {
        const name = props.name
        const matched = route.matched[depth]
        const component = matched && matched.components[name]
        if (!matched || !component) {
            return h()        
        }     
        ...
        return h(conponent, ...)     
    }
}

嵌套命名视图

官网例子:
/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+
<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
  <router-view/>
  <router-view name="helper"/>
</div>
// 路由配置
{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}
  • 命名视图的路由匹配规则并没有改变,这里只是多个一个children属性,同时children中的每个元素的内容又符合路由配置内容,即可以看做一个单独的route。所以在router初始化并记录路由的record时,对children进行了递归处理:
// create-route-map.js

function addRouteRecord(
    pathList: Array<string>,
    pathMap: Dictionary<RouteRecord>,
    nameMap: Dictionary<RouteRecord>,
    route: RouteConfig,
    parent?: RouteRecord,
    matchAs?: string
) {
    const { path, name } = route
    ...
    const record: RouteRecord = {
        components: route.components || {default: route.component},
        parent, // 记录父record
        ...
    }
    if (route.children) {
        route.children.forEach(child => {
            // 递归调用addRouteRecord函数,父record会赋值到child的record.parent上
            addRouteRecord(pathList, pathMap, nameMap, child, record)        
        })    
    }
    ...
}
  • 回到开头的官网例子,如果我们访问的是/settings/emails,在匹配到对应的路由后,由于无法取得components[helper],所以 router-view name=“helper” 会被vue渲染成一个空节点。
<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar/>
  <router-view/>
  <router-view name="helper"/>
</div>

// 路由配置
{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}

// ./components/view.js

export default {
    ...
    props: {
        name: {
            type: String,
            default: 'default'        
        }    
    },
    render(...) {
        ...
        const matched = route.matched[depth]
        const component = matched && matched.components[name]
        // 这里取不到 matched.components["helper"]
        if (!matched || !component) {
            ...
            return h()        
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值