slot 给 BaseComponent 却无法正确渲染的原因,看下图:

这张图与上一张图相同,在子组件中打印 this.$vnode ,标注中的 context 引用着 VNode 被创建时所在的组件实例,由于 this.$vnode 中引用的 VNode 对象是在父组件中被创建的,所以 this.$vnode 中的 context 引用着父实例。理论上图中标注的两个 context 应该是相等的:
复制代码 代码如下:
console.log(this.$vnode.context === this.$vnode.componentOptions.children[0].context) // true
而 Vue 内部做了一件很重要的事儿,即上面那个表达式必须成立,才能够正确处理具名 slot ,否则即使 slot 具名也不会被考虑,而是被作为默认插槽。这就是高阶组件中不能正确渲染 slot 的原因。
那么为什么高阶组件中上面的表达式就不成立了呢?那是因为由于高阶组件的引入,在原本的父组件与子组件之间插入了一个组件( 也就是高阶组件 ),这导致在子组件中访问的 this.$vnode 已经不是原来的父组件中的 VNode 片段了,而是高阶组件的 VNode 片段,所以此时 this.$vnode.context 引用的是高阶组件,但是我们却将 slot 透传, slot 中的 VNode 的 context 引用的还是原来的父组件实例,所以这就造成了以下表达式为假:
复制代码 代码如下:
console.log(this.$vnode.context === this.$vnode.componentOptions.children[0].context) // false
最终导致具名插槽被作为默认插槽,从而渲染不正确。
而解决办法也很简单,只需要手动设置一下 slot 中 VNode 的 context 值为高阶组件实例即可,修改高阶组件如下:
hoc.js
function WithConsole (WrappedComponent) {
return {
mounted () {
console.log('I have already mounted')
},
props: WrappedComponent.props,
render (h) {
const slots = Object.keys(this.$slots)
.reduce((arr, key) => arr.concat(this.$slots[key]), [])
// 手动更正 context
.map(vnode => {
vnode.context = this._self
return vnode
})
return h(WrappedComponent, {
on: this.$listeners,
props: this.$props,
attrs: this.$attrs
}, slots)
}
}
}
现在,都能够正常渲染啦,如下图:
