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

前言

在上一篇文章Vue-Router入门(四) :命名路由我们学习了命名路由name的含义,本节我们来学习一下与命名路由相似的命名视图。

命名视图是什么

Vue-Router入门(一) :初识路由中,我们学习到了路由通过 router-view 组件来渲染对应的组件内容。而命名视图则允许我们在同一个路由下,通过给 router-view 设置不同的 name 属性,来同时渲染多个组件,并将它们分别渲染到不同的命名区域。

例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。

html 复制代码
<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):

js 复制代码
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。

js 复制代码
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对应的组件。

js 复制代码
 return () => {
      const route = routeToDisplay.value
      const currentName = props.name
      const matchedRoute = matchedRouteRef.value
      const ViewComponent = matchedRoute && matchedRoute.components![currentName]

紧接着。获取组件props参数,利用h函数创建虚拟dom得到component。

获取 props参数的源码

js 复制代码
      // 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的源码

js 复制代码
      const component = h(
        ViewComponent,
        assign({}, routeProps, attrs, {
          onVnodeUnmounted,
          ref: viewRef,
        })
      )

返回插槽写法与component,以兼容动态组件。最终RouterView就是等于RouterViewImpl包括props参数与slot插槽。

js 复制代码
      return (
        normalizeSlot(slots.default, { Component: component, route }) ||
        component
      )

总结下命名视图的步骤

  1. 根据name参数获取了currentName ,拿着currentName获取了ViewComponent
  2. 获取组件props参数,利用h函数创建虚拟dom得到component。
  3. 返回插槽写法与component,以此来兼容动态组件。 最终RouterView就是等于RouterViewImpl包括props参数与slot插槽。

源码

js 复制代码
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
      )
    }
  },
})
相关推荐
麒麟而非淇淋28 分钟前
AJAX 入门 day1
前端·javascript·ajax
2401_8581205331 分钟前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢35 分钟前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写2 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.2 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html
快乐牌刀片882 小时前
web - JavaScript
开发语言·前端·javascript
miao_zz3 小时前
基于HTML5的下拉刷新效果
前端·html·html5
Zd083 小时前
14.其他流(下篇)
java·前端·数据库
藤原拓远3 小时前
JAVAWeb-XML-Tomcat(纯小白下载安装调试教程)-HTTP
前端·firefox
重生之我在20年代敲代码3 小时前
HTML讲解(一)body部分
服务器·前端·html