探索Vue高阶组件的使用(6)

渲染结果如下:

上图中蓝色框是 BaseComponent 组件渲染的内容,是正常的。红色框是高阶组件渲染的内容,可以发现无论是具名插槽还是默认插槽全部丢失。其原因很简单,就是因为我们在高阶组件中没有将分发的插槽内容透传给被包装组件( WrappedComponent ),所以我们尝试着修改高阶组件:

hoc.js

function WithConsole (WrappedComponent) {
 return {
  mounted () {
   console.log('I have already mounted')
  },
  props: WrappedComponent.props,
  render (h) {

   // 将 this.$slots 格式化为数组,因为 h 函数第三个参数是子节点,是一个数组
   const slots = Object.keys(this.$slots)
    .reduce((arr, key) => arr.concat(this.$slots[key]), [])

   return h(WrappedComponent, {
    on: this.$listeners,
    attrs: this.$attrs,
    props: this.$props
   }, slots) // 将 slots 作为 h 函数的第三个参数
  }
 }
}

好啦,大功告成刷新页面,如下:

纳尼:scream:?我们发现,分发的内容确实是渲染出来了,不过貌似顺序不太对。。。。。。蓝色框是正常的,在具名插槽与默认插槽的中间是有分界线( =========== )的,而红色框中所有的插槽全部渲染到了分界线( =========== )的下面,看上去貌似具名插槽也被作为默认插槽处理了。这到底是怎么回事呢?

想弄清楚这个问题,就回到了文章开始时我提到的一点,即你需要对 Vue 的实现原理有所了解才行,否则无解。接下来就从原理触发讲解如何解决这个问题。这个问题的根源在于: Vue 在处理具名插槽的时候会考虑作用域的因素 。不明白没关系,我们一点点分析。

首先补充一个提示: Vue 会把模板( template )编译成渲染函数( render ) ,比如如下模板:

<div>
 <h2 slot="slot1">BaseComponent slot</h2>
</div>

会被编译成如下渲染函数:

var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", [ _c("h2", {  attrs: { slot: "slot1" },  slot: "slot1" }, [  _vm._v("BaseComponent slot") ]) ])}

想要查看一个组件的模板被编译后的渲染函数很简单,只需要在访问 this.$options.render 即可。观察上面的渲染函数我们发现普通的 DOM 是通过 _c 函数创建对应的 VNode 的。现在我们修改模板,模板中除了有普通