Vue-Router入门(五) :命名视图

前言

在上一篇文章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
      )

总结下命名视图的步骤

  1. 根据name参数获取了currentName,拿着currentName获取了ViewComponent
  2. 获取组件props参数,利用h函数创建虚拟dom得到component。
  3. 返回插槽写法与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 作者:笨鸟更要先飞

(0)
上一篇 2024年3月29日 上午10:44
下一篇 2024年3月29日 下午4:05

相关推荐

发表回复

登录后才能评论