前言
在上一篇文章Vue-Router入门(四) :命名路由我们学习了命名路由name的含义,本节我们来学习一下与命名路由相似的命名视图。
命名视图是什么
在Vue-Router入门(一) :初识路由中,我们学习到了路由通过 router-view
组件来渲染对应的组件内容。而命名视图则允许我们在同一个路由下,通过给 router-view
设置不同的 name
属性,来同时渲染多个组件,并将它们分别渲染到不同的命名区域。
例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
但是又因为一个视图对应一个组件,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
注意点:
- router-view的name属性默认是default,default的组件就是router-view对应的。
- LeftSidebar、RightSidebar是简写,当视图的name属性与组件名称相同时可以简写为一个。
源码解析
源码位置,
当然,如果你不想去vue-router的源码中去寻找的话,作者也在文章的最后贴上了主要逻辑的源码。
首先,vue-router
通过VUE
中的defineComponent
来创建组件RouterViewImpl,该组件包括name与route两个proprs参数,name的默认值就是default。
export const RouterViewImpl = /*#__PURE__*/ defineComponent({
name: 'RouterView',
inheritAttrs: false,
//这里
props: {
name: {
type: String as PropType<string>,
default: 'default',
},
route: Object as PropType<RouteLocationNormalizedLoaded>,
},
然后在return
中通过const currentName = props.name
,来获取name,接下来拿着currentName获取了ViewComponent。很明显ViewComponent就是name对应的组件。
return () => {
const route = routeToDisplay.value
const currentName = props.name
const matchedRoute = matchedRouteRef.value
const ViewComponent = matchedRoute && matchedRoute.components![currentName]
紧接着。获取组件props参数,利用h函数创建虚拟dom得到component。
获取 props参数的源码
// props from route configuration
const routePropsOption = matchedRoute.props[currentName]
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null
用h函数创建虚拟dom得到component的源码
const component = h(
ViewComponent,
assign({}, routeProps, attrs, {
onVnodeUnmounted,
ref: viewRef,
})
)
返回插槽写法与component,以兼容动态组件。最终RouterView
就是等于RouterViewImpl
包括props
参数与slot
插槽。
return (
normalizeSlot(slots.default, { Component: component, route }) ||
component
)
总结下命名视图的步骤
- 根据name参数获取了currentName,拿着currentName获取了ViewComponent。
- 获取组件props参数,利用h函数创建虚拟dom得到component。
- 返回插槽写法与component,以此来兼容动态组件。 最终RouterView就是等于RouterViewImpl包括props参数与slot插槽。
源码
export const RouterViewImpl = /*#__PURE__*/ defineComponent({
name: 'RouterView',
inheritAttrs: false,
props: {
name: {
type: String as PropType<string>,
default: 'default',
},
route: Object as PropType<RouteLocationNormalizedLoaded>,
},
compatConfig: { MODE: 3 },
setup(props, { attrs, slots }) {
__DEV__ && warnDeprecatedUsage()
const injectedRoute = inject(routerViewLocationKey)!
const routeToDisplay = computed<RouteLocationNormalizedLoaded>(
() => props.route || injectedRoute.value
)
const injectedDepth = inject(viewDepthKey, 0)
const depth = computed<number>(() => {
let initialDepth = unref(injectedDepth)
const { matched } = routeToDisplay.value
let matchedRoute: RouteLocationMatched | undefined
while (
(matchedRoute = matched[initialDepth]) &&
!matchedRoute.components
) {
initialDepth++
}
return initialDepth
})
const matchedRouteRef = computed<RouteLocationMatched | undefined>(
() => routeToDisplay.value.matched[depth.value]
)
provide(
viewDepthKey,
computed(() => depth.value + 1)
)
provide(matchedRouteKey, matchedRouteRef)
provide(routerViewLocationKey, routeToDisplay)
const viewRef = ref<ComponentPublicInstance>()
watch(
() => [viewRef.value, matchedRouteRef.value, props.name] as const,
([instance, to, name], [oldInstance, from, oldName]) => {
if (to) {
to.instances[name] = instance
if (from && from !== to && instance && instance === oldInstance) {
if (!to.leaveGuards.size) {
to.leaveGuards = from.leaveGuards
}
if (!to.updateGuards.size) {
to.updateGuards = from.updateGuards
}
}
}
if (
instance &&
to &&
(!from || !isSameRouteRecord(to, from) || !oldInstance)
) {
;(to.enterCallbacks[name] || []).forEach(callback =>
callback(instance)
)
}
},
{ flush: 'post' }
)
//return 函数
return () => {
const route = routeToDisplay.value
const currentName = props.name
const matchedRoute = matchedRouteRef.value
const ViewComponent =
matchedRoute && matchedRoute.components![currentName]
if (!ViewComponent) {
return normalizeSlot(slots.default, { Component: ViewComponent, route })
}
const routePropsOption = matchedRoute.props[currentName]
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null
const onVnodeUnmounted: VNodeProps['onVnodeUnmounted'] = vnode => {
if (vnode.component!.isUnmounted) {
matchedRoute.instances[currentName] = null
}
}
//用h函数创建虚拟dom得到component
const component = h(
ViewComponent,
assign({}, routeProps, attrs, {
onVnodeUnmounted,
ref: viewRef,
})
)
if (
(__DEV__ || __FEATURE_PROD_DEVTOOLS__) &&
isBrowser &&
component.ref
) {
const info: RouterViewDevtoolsContext = {
depth: depth.value,
name: matchedRoute.name,
path: matchedRoute.path,
meta: matchedRoute.meta,
}
const internalInstances = isArray(component.ref)
? component.ref.map(r => r.i)
: [component.ref.i]
internalInstances.forEach(instance => {
instance.__vrv_devtools = info
})
}
//返回插槽写法与component
return (
normalizeSlot(slots.default, { Component: component, route }) ||
component
)
}
},
})
原文链接:https://juejin.cn/post/7351430820714889242 作者:笨鸟更要先飞