#このチュートリアルの動作環境: Windows7 システム、vue3 バージョン、DELL G3 コンピューター。vue では、高階コンポーネントは実際には高階関数、つまりコンポーネント関数を返す関数です。高次コンポーネントの特徴: 1. 副作用のない純粋な関数であり、元のコンポーネントを変更する必要はありません、つまり、元のコンポーネントを変更することはできません; 2. データ (props) を考慮しません。渡され、新しく生成されたコンポーネントはデータのソースを気にしません。 ; 3. 受け取った props はパッケージ化されたコンポーネントに渡される必要があります。つまり、元のコンポーネント props はパッケージ化コンポーネントに直接渡されます。 4. 高-オーダーコンポーネントは、プロパティを完全に追加、削除、変更できます。
高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动 高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源 高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件 高阶组件完全可以添加、删除、修改 props
<template> <div> <p @click="Click">props: {{test}}</p> </div> </template> <script> export default { name: 'Base', props: { test: Number }, methods: { Click () { this.$emit('Base-click') } } } </script>
export default consoleMixin { mounted () { console.log('haha') } }
<template> <div> <p @click="Click">props: {{test}}</p> </div> </template> <script> export default { name: 'Base', props: { test: Number }, mixins: [ consoleMixin ], methods: { Click () { this.$emit('Base-click') } } } </script>
export default { name: 'Base', props: {...}, mixins: [...] methods: {...} }
import Base from './Base.vue' console.log(Base)
export default function Console (BaseComponent) { return { template: '<wrapped v-on="$listeners" v-bind="$attrs"/>', components: { wrapped: BaseComponent }, mounted () { console.log('haha') } } }
Console.js
export default function Console (BaseComponent) { return { mounted () { console.log('haha') }, render (h) { return h(BaseComponent, { on: this.$listeners, attrs: this.$attrs, }) } } }
テンプレートをレンダリング関数に書き換えました。一見問題がないようですが、実はまだ問題があります。上記のコードでは、BaseComponent コンポーネントがまだ受信できません。 props. どうして、すでに h 関数に入っていないのですか? 2 つのパラメータの間に attrs を渡しましたか? なぜそれを受け取ることができないのですか?もちろん受け取ることはできません。Attrs は props として宣言されていない属性を参照するため、レンダリング関数に props パラメータを追加する必要があります:
export default function Console (BaseComponent) { return { mounted () { console.log('haha') }, render (h) { return h(BaseComponent, { on: this.$listeners, attrs: this.$attrs, props: this.$props }) } } }
それでも機能しません。常に空のオブジェクトです。ここで props は上位コンポーネントのオブジェクトですが、上位コンポーネントは props を宣言していないため、別の props
export default function Console (BaseComponent) { return { mounted () { console.log('haha') }, props: BaseComponent.props, render (h) { return h(BaseComponent, { on: this.$listeners, attrs: this.$attrs, props: this.$props }) } } }
ok を宣言する必要があります。同様の上位コンポーネントは次のとおりです。完了しましたが、まだ可能であれば実装するだけです。プロパティを透過的に送信し、イベントを透過的に送信すると、スロットだけが残ります。Base コンポーネントを変更して、名前付きスロットとデフォルト スロットを追加します。Base.vue
<template> <div> <span @click="handleClick">props: {{test}}</span> <slot name="slot1"/> <!-- 具名插槽 --></slot> <p>===========</p> <slot><slot/> <!-- 默认插槽 --> </div> </template> <script> export default { ... } </script> <template> <div> <Base> <h2 slot="slot1">BaseComponent slot</h2> <p>default slot</p> </Base> <wrapBase> <h2 slot="slot1">EnhancedComponent slot</h2> <p>default slot</p> </wrapBase> </div> </template> <script> import Base from './Base.vue' import hoc from './Console.js' const wrapBase = Console(Base) export default { components: { Base, wrapBase } } </script>
ここでの実行結果は、wrapBase にスロットがないということです。したがって、上位コンポーネントを変更する必要があります
function Console (BaseComponent) { return { mounted () { console.log('haha') }, props: BaseComponent.props, render (h) { // 将 this.$slots 格式化为数组,因为 h 函数第三个参数是子节点,是一个数组 const slots = Object.keys(this.$slots) .reduce((arr, key) => arr.concat(this.$slots[key]), []) return h(BaseComponent, { on: this.$listeners, attrs: this.$attrs, props: this.$props }, slots) // 将 slots 作为 h 函数的第三个参数 } } }
この時点では、スロットの内容は確かにレンダリングされますが、順序が正しくありません。そしてすべての上位コンポーネントが最後までレンダリングされます。 。実際、Vue は名前付きスロットを処理するときにスコープ要素を考慮します。まず、Vue はテンプレートをレンダリング関数 (render) にコンパイルします。たとえば、次のテンプレート:
<div> <p slot="slot1">Base slot</p> </div>
は次のレンダリング関数にコンパイルされます。 :
var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", [ _c("div", { attrs: { slot: "slot1" }, slot: "slot1" }, [ _vm._v("Base slot") ]) ]) }
上記のレンダリング関数を観察すると、通常の DOM が _c 関数を通じて対応する VNode を作成していることがわかります。次に、テンプレートを変更します。通常の DOM に加えて、テンプレートには次のようなコンポーネントもあります:
<div> <Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base> </div>
そのレンダリング関数
var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c( "div", [ _c("Base", [ _c("p", { attrs: { slot: "slot1" }, slot: "slot1" }, [ _vm._v("Base slot") ]), _vm._v(" "), _c("p", [_vm._v("default slot")]) ]) ], ) }
我们发现无论是普通DOM还是组件,都是通过 _c 函数创建其对应的 VNode 的 其实 _c 在 Vue 内部就是 createElement 函数。createElement 函数会自动检测第一个参数是不是普通DOM标签如果不是普通DOM标签那么 createElement 会将其视为组件,并且创建组件实例,注意组件实例是这个时候才创建的 但是创建组件实例的过程中就面临一个问题:组件需要知道父级模板中是否传递了 slot 以及传递了多少,传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢?很简单,假如组件的模板如下
<div> <Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base> </div>
父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到
<Base> <p slot="slot1">Base slot</p> <p>default slot</p> </Base>
如果能够通过父级拿到这段模板对应的 VNode,那么子组件就知道要渲染哪些 slot 了,其实 Vue 内部就是这么干的,实际上你可以通过访问子组件的 this.$vnode 来获取这段模板对应的 VNode
this.$vnode 并没有写进 Vue 的官方文档
子组件拿到了需要渲染的 slot 之后进入到了关键的一步,这一步就是导致高阶组件中透传 slot 给 Base组件 却无法正确渲染的原因 children的VNode中的context引用父组件实例 其本身的context也会引用本身实例 其实是一个东西
console.log(this. vnode.context===this.vnode.componentOptions.children[0].context) //ture
而 Vue 内部做了一件很重要的事儿,即上面那个表达式必须成立,才能够正确处理具名 slot,否则即使 slot 具名也不会被考虑,而是被作为默认插槽。这就是高阶组件中不能正确渲染 slot 的原因
即 高阶组件中 本来时父组件和子组件之间插入了一个组件(高阶组件),而子组件的 this.$vnode其实是高阶组件的实例,但是我们将slot透传给子组件,slot里 VNode 的context实际引用的还是父组件 所以
console.log(this.vnode.context === this.vnode.componentOptions.children[0].context) // false
最终导致具名插槽被作为默认插槽,从而渲染不正确。
决办法也很简单,只需要手动设置一下 slot 中 VNode 的 context 值为高阶组件实例即可
function Console (Base) { return { mounted () { console.log('haha') }, props: Base.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) } } }
说明白就是强制把slot的归属权给高阶组件 而不是 父组件 通过当前实例 _self 属性访问当实例本身,而不是直接使用 this,因为 this 是一个代理对象
以上がvue の上位コンポーネントとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。