在vue-router中配置函数式组件提高组件复用性

412 阅读2分钟

我正在参加「掘金·启航计划」

遇到的问题场景:
  1. 初始需求是一个列表组件L,一个布局组件C,路由配置如下
{
    path: "/home",
    component: C,
    children: [
      {
        path: 'list',
        component: L
      }
    ]
  }

dom的表现形式为<C> <L /> </C>

  1. 修改需求,增加两种新的布局样式组件AB,需要在不同情况下展示不一样的列表布局样式,但<C> <L /> </C>这个保持不变,只需要在最外面再包裹一层,如下所示

<A> <C> <L /> </C> </A>

或者

<B> <C> <L /> </C> </B>

如何尽量的复用布局组件与列表业务组件的组合关系?

可用的解决方案
  • 第一种解决思路

再创建两个路由组件A1和B1,此时总共有6个组件文件

A1: <A> <C></C> </A>

B1: <B> <C></C> </B>

然后配置路由

{
    path: "/home",
    component: process.env.platform ? A1 : B1,
    children: [
      {
        path: 'list',
        component: L
      }
    ]
  }

缺点:如果有多种不同的列表组件和布局组件的组合,将会多创建出了很多组件文件,冗余度高

  • 第二种解决思路

    为了避免创建过多的文件,可考虑直接在一个组件上引入多个组件解决

     // Main.vue
     Vue.extend({
       render (h) {
         const targetLayout = process.env.platform ? A : B
         return h(targetLayout, [C])
       }
     }
    

    缺点:需要提前import所有的布局组件,且每次增加布局组件都需要修改当前文件

  • 第三种解决思路

    使用函数式组件,通过组合的设计模式,可以灵活的组装不同的布局组合,并对布局组件的嵌套进行了解耦

    // layoutRender.js
    export default function(containerLayout, homeLayout) {
      return {
        name: "LayoutRender",
        functional: true,
        render: function (h, context) {
          const scopedSlots = {
            default(){ return h(homeLayout) }
          }
          return h(containerLayout, {
            routerView: true, // 这个属性必须设置
            scopedSlots
          })
        }
      }
    }
    

    此时的路由配置如下:

    {
        path: "/home",
        component: LayoutRender(A, C),
        children: [
          {
            path: 'list',
            component: L
          }
        ]
      }
    
进阶阅读-介绍router-view组件的部分细节(不感兴趣可略过)

RouterView组件是一个函数式组件,主要是vue-router库用来渲染子路由组件的

RouterView组件在判断渲染哪一个路由组件时,首先会不断的迭代父组件的vnode直至到达根节点,寻找该vnode的data属性有没有包含routerView属性,如果有则把depth加1,最终计算的值将赋给routerViewDepth,当使用嵌套路由时,匹配到的路由组件会有多个,是个数组类型,通过routerViewDepth则可以获取数组里面对应的元素组件作为路由组件并进行渲染。

部分源码如下

var depth = 0;
while (parent && parent._routerRoot !== parent) {
  var vnodeData = parent.$vnode ? parent.$vnode.data : {};
  if (vnodeData.routerView) {
    depth++;
  }
  
  parent = parent.$parent;
}
data.routerViewDepth = depth;

var matched = route.matched[depth];
// 获取需要渲染的子组件
var component = matched && matched.components[name];