vue-router 命名视图(Named Views)完全指南:同一路由渲染多个组件的布局方案
【免费下载链接】vue-router🚦 The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router
导读
在 Vue 2 单页应用中,vue-router默认只提供一个视图出口(<router-view>),一条路由对应渲染一个组件。但在真实项目中,我们常常需要"同屏多视图"的布局——例如一个同时包含主内容区(main)与侧边栏(sidebar)的页面。命名视图(Named Views)正是为此设计的:它允许在一条路由下同时渲染多个组件到多个具名出口中。本文基于本仓库官方文档与源码,系统讲解命名视图的定义方式、底层实现原理、嵌套命名视图的进阶布局,以及如何用仓库内示例与端到端测试验证效果。
什么是命名视图:从"嵌套"到"平铺"的布局思路
什么时候需要命名视图
常规做法是把视图一层层嵌套:父路由渲染父组件,父组件内部再放一个<router-view>渲染子路由组件(可参考仓库文档 嵌套路由)。但有些布局无法用嵌套表达——例如"一个页面同时显示主内容 + 侧边栏 + 底部栏",这几个区域是平级的,而不是父子包含关系。
此时就轮到命名视图登场。正如仓库官方文档(docs-gitbook/de/essentials/named-views.md)所述:与其只有一个视图出口,不如提供多个,并分别给它们命名。未命名的<router-view>会被自动赋予默认名default。
基本写法:三个具名出口
在模板中放置多个<router-view>,用name属性区分:
<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>- 第一个没有
name属性,等价于name="default"; - 第二、三个分别名为
a、b。
路由配置:必须使用components(复数)
视图由组件渲染,多个视图自然需要多个组件。因此同一条路由下配置多个组件时,必须使用components(带 s 的复数形式)选项,而不是component:
const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })components是一个对象,键对应<router-view>的name,值是对应的组件。上例中:default出口渲染Foo、a出口渲染Bar、b出口渲染Baz。
底层原理:RouterView 如何按名称取组件
name属性的默认值
命名视图的核心实现位于 src/components/view.js。它是一个函数式组件(functional),其props定义如下(src/components/view.js):
export default { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default' } }, ... }从这里可以确认:不带name的<router-view>的默认名称就是字符串default,与官方文档描述完全一致。
匹配过程:depth 与 matched.components[name]
RouterView的渲染逻辑(src/components/view.js)按以下步骤工作:
- 计算视图深度(depth):遍历父级链,统计出现了多少个带
routerView标记的祖先节点,从而知道当前出口处于路由记录的哪一层(用于配合嵌套路由)。 - 按深度取出匹配的路由记录:
const matched = route.matched[depth],即当前路由在该深度上匹配到的记录。 - 按名称取组件:
const component = matched && matched.components[name]——这正是命名视图的"命根子":组件来源于路由记录(RouteRecord)上的components字典,用当前出口的名称作为键去取。 - 取不到就渲染空节点:如果当前没有匹配记录,或该记录上没有对应名称的组件(
!matched || !component),则渲染一个空节点h(),对应的出口位置什么都不显示。
路由记录如何归一化 components
无论你写的是单数component还是复数components,最终都会在构建路由表时被归一化为字典。源码 src/create-route-map.js 中的addRouteRecord是这样处理的:
const record: RouteRecord = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, ... props: route.props == null ? {} : route.components ? route.props : { default: route.props } }关键结论:
- 只写
component: Foo时,等价于components: { default: Foo }——所以"单视图"本质是"默认名视图"的特例; - 当使用
components复数形式时,props也应改为字典形式(按视图名称分别传 props),源码对此做了显式区分(详见下文"命名视图与 props"一节)。
类型定义也能佐证这一点:types/router.d.ts 中定义了RouteConfigSingleView(含component)与RouteConfigMultipleViews(含components?: Dictionary<Component>),两者共同组成RouteConfig联合类型。
仓库内的完整可运行示例
官方文档提到的工作 Demo 对应的就是仓库中的 examples/named-views/app.js(页面骨架见 examples/named-views/index.html)。它比文档示例更进一步,演示了同一条路由配置的两套具名组件映射:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } const Baz = { template: '<div>baz</div>' } const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', // a single route can define multiple named components // which will be rendered into <router-view>s with corresponding names. components: { default: Foo, a: Bar, b: Baz } }, { path: '/other', components: { default: Baz, a: Bar, b: Foo } } ] }) new Vue({ router, template: ` <div id="app"> <h1>Named Views</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/other">/other</router-link></li> </ul> <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> </div> ` }).$mount('#app')可以这样运行验证(仓库只读,仅介绍运行方式):
yarn install yarn run dev # 浏览器访问 http://localhost:8080/named-views/导航到/other时,同一组三个出口会切换到另一套组件映射(default变Baz、b变Foo),直观体现了"一条路由 ↔ 多组件"的能力。
端到端测试中的行为断言
仓库的端到端测试 test/e2e/specs/named-views.js 对上述行为做了完整断言,可作为行为规范参考:
- 访问
/named-views/时:.view.one文本为foo、.view.two为bar、.view.three为baz; - 点击链接跳转到
/named-views/other后:.view.one变为baz、.view.two仍为bar、.view.three变为foo; - 直接刷新访问
/named-views/other(模拟首次进入),断言结果同样成立。
这证明命名视图的组件解析与 URL 完全联动,且能正确处理"直接访问 + 应用内跳转"两种场景。
进阶:嵌套命名视图构建复杂布局
命名视图可以与嵌套路由组合,构建类似"设置中心"的多面板布局。英文版官方文档(docs/guide/essentials/named-views.md)给出了一个经典的 Settings 示例:外层是UserSettings页面,内部同时存在普通导航组件Nav、默认出口以及一个名为helper的具名出口,不同子路由(/settings/emails与/settings/profile)在默认出口渲染不同内容,同时helper出口可选择性渲染预览组件。
UserSettings 组件的模板
<!-- UserSettings.vue --> <div> <h1>User Settings</h1> <NavBar/> <router-view/> <router-view name="helper"/> </div>其中:
NavBar是普通组件,与路由无关;- 无
name的<router-view/>渲染默认组件; <router-view name="helper"/>渲染名为helper的组件——如果当前路由没有配置helper,该出口渲染为空节点(对应上文源码中!component时return h()的行为)。
嵌套命名视图的路由配置
{ path: '/settings', // You could also have named views at the top component: UserSettings, children: [{ path: 'emails', component: UserEmailsSubscriptions }, { path: 'profile', components: { default: UserProfile, helper: UserProfilePreview } }] }要点:
- 顶层
/settings用单数component: UserSettings(渲染外层壳); - 子路由
emails使用单数component,此时helper出口为空; - 子路由
profile使用复数components,同时填充默认出口与helper出口。
结合 src/components/view.js 的深度计算逻辑可知:子路由中的<router-view>会因父级UserSettings中存在 routerView 标记而使depth递增,从而正确匹配route.matched[depth]这一层记录——这正是嵌套命名视图在源码层面的支撑机制。
命名视图与 props:字典形式的传参
使用命名视图时,如果还需要向各视图组件传 props,props同样要用字典形式(每个名称一个配置),与components一一对应。这一点由类型定义明确约束(types/router.d.ts):
interface RouteConfigMultipleViews extends _RouteConfigBase { components?: Dictionary<Component> props?: Dictionary<boolean | Object | RoutePropsFunction> }底层解析位于 src/components/view.js:渲染时会取matched.props && matched.props[name],即当前视图名称对应的 props 配置,随后通过fillPropsinData将解析出的 props 传给组件;未被组件声明为 props 的字段会被降级为普通 attrs。resolveProps支持三种形态(src/components/view.js):
object:直接作为 props 传入;function:接收route参数、返回 props 对象(常用于动态传参);boolean:true时把route.params作为 props 传入。
常见误区与注意事项
- 单复数混淆:多视图必须用
components,用了单数component则只有默认出口生效。源码route.components || { default: route.component }也解释了为何混用容易困惑——单数写法会被归一化为default键。 - 默认名记忆:不带
name的出口叫default,配置时写default: Foo即可覆盖它,不必额外加name="default"。 - 未配置即空白:某条路由若没给某个具名出口提供组件,该出口渲染为空节点,不会报错(源码 src/components/view.js)。这在"同一布局、部分区域按路由条件显示"的场景中非常实用。
- 嵌套时注意层级:子路由中要使用命名视图,需确保父组件模板里已放置对应名称的
<router-view>,且通过父链上的routerView标记让深度计算正确(源码 src/components/view.js)。 - 命名路由与默认子路由的坑:源码 src/create-route-map.js 会警告:带
name且含有默认子路由(path: '')的父路由,使用:to="{ name: ... }"导航时默认子路由不会渲染,应改用子路由自己的名字。
小结
命名视图是 vue-router 构建"同屏多区"布局的标准方案:模板侧用多个具名<router-view>声明出口,路由配置侧用components字典(键为出口名)映射组件,源码侧由RouterView按名称从route.matched[depth].components取值渲染。本文结合 src/components/view.js、src/create-route-map.js、examples/named-views/app.js 与 test/e2e/specs/named-views.js 展示了从"能用"到"懂原理"的完整链路。更多相关概念可继续阅读仓库文档中的 嵌套路由 与 命名路由。
【免费下载链接】vue-router🚦 The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考