vue では、高階コンポーネントは実際には高階関数、つまりコンポーネント関数を返す関数です。高次コンポーネントの特徴: 1. 副作用のない純粋な関数であり、元のコンポーネントを変更する必要はありません、つまり、元のコンポーネントを変更することはできません; 2. データ (props) を考慮しません。渡され、新しく生成されたコンポーネントはデータのソースを気にしません。 ; 3. 受け取った props はパッケージ化されたコンポーネントに渡される必要があります。つまり、元のコンポーネント props はパッケージ化コンポーネントに直接渡されます。 4. 高-オーダーコンポーネントは、プロパティを完全に追加、削除、変更できます。

#このチュートリアルの動作環境: Windows7 システム、vue3 バージョン、DELL G3 コンピューター。
高次コンポーネントの概要
vue 高次コンポーネントの理解。React ではコンポーネントは再利用されたコードで実装されますが、Vue ではミックスインで実装されます。また、Vue で高次コンポーネントを実装するのは難しく、React ほど単純ではないため、公式ドキュメントには高次コンポーネントの概念がいくつか欠けています。実際、Vue のミックスインもミックスインに置き換えられます。ソースコードを見ると、Vue についての理解が深まります理解
いわゆる高階コンポーネントは、実際には高階関数、つまりコンポーネント関数を返す関数です。 Vueで?上位コンポーネントには次の特性があることに注意してください。
1 2 3 4 | 高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源
高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件
高阶组件完全可以添加、删除、修改 props
|
ログイン後にコピー
上位コンポーネントの例
Base.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <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>
|
ログイン後にコピー
Vue コンポーネントには主に props、event、および 3 つのポイントがあります。スロット。 Base コンポーネント コンポーネントの場合、数値型のプロパティ (つまり test) を受け取り、カスタム イベントをトリガーします。イベントの名前は、Base-click (スロットなし) です。コンポーネントを次のように使用します:
次に、マウントされるたびに「笑」という文を出力するためのベースコンポーネントコンポーネントが必要です。同時に、これは多くのコンポーネントの要件である可能性があります。 mixins メソッドに従って、これを行うことができます。最初に mixins
1 2 3 4 5 | export default consoleMixin {
mounted () {
console.log( 'haha' )
}
}
|
ログイン後にコピー
を定義し、次に Base コンポーネントで consoleMixin をミックスします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <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>
|
ログイン後にコピー
Base コンポーネントをこのように使用する場合、ははは各実装が完了するとメッセージが出力されますが、今度は同じ機能を実現するために高次コンポーネントを使用します。高次コンポーネントの定義を思い出してください。コンポーネントをパラメータとして受け取り、新しいコンポーネントを返します。では、現時点で考えなければならないのは、Vue のコンポーネントとは何なのかということです。 Vue のコンポーネントは関数ですが、それは最終結果です。たとえば、単一ファイル コンポーネントのコンポーネント定義は、実際には次のような通常のオプション オブジェクトです:
1 2 3 4 5 6 | export default {
name: 'Base' ,
props: {...},
mixins: [...]
methods: {...}
}
|
ログイン後にコピー
これは純粋なオブジェクトではありませんか?
1 2 | import Base from './Base.vue'
console.log(Base)
|
ログイン後にコピー
Base とは何ですか? これは JSON オブジェクトです。これをコンポーネントのコンポーネントに追加すると、Vu は最終的にこのパラメーター (つまりオプション) を使用してインスタンスのコンストラクターを構築します。 Vue のコンポーネントは関数ですが、導入前はまだ単なるオプション オブジェクトであるため、Vue のコンポーネントは最初は単なるオブジェクト、つまり上位コンポーネントは関数であることが容易に理解できます。純粋なオブジェクトを受け入れ、新しい純粋なオブジェクトを返します
1 2 3 4 5 6 7 8 9 10 11 | export default function Console (BaseComponent) {
return {
template: '<wrapped v-on="$listeners" v-bind="$attrs"/>' ,
components: {
wrapped: BaseComponent
},
mounted () {
console.log( 'haha' )
}
}
}
|
ログイン後にコピー
ここでのコンソールは上位コンポーネントであり、コンポーネントに渡されたパラメータ BaseComponent を受け取り、新しいコンポーネントを返し、BaseComponent をサブコンポーネントとして使用します新しいコンポーネントを作成し、出力するためにマウントされたフック関数を設定します (笑)。ミックスインでも同じことができます。問題は、サブコンポーネント Base を変更していないということです。ここの $listeners $attrs は、実際には props とイベントを透過的に送信しています。本当にこれで問題は完全に解決するのでしょうか?いいえ、まず第一に、テンプレート オプションは Vue のフル バージョンでのみ使用でき、ランタイム バージョンでは使用できないため、少なくともテンプレート (template) の代わりにレンダリング関数 (render) を使用する必要があります。
Console.js
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 パラメータを追加する必要があります:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <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 にスロットがないということです。したがって、上位コンポーネントを変更する必要があります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function Console (BaseComponent) {
return {
mounted () {
console.log( 'haha' )
},
props: BaseComponent.props,
render (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)
}
}
}
|
ログイン後にコピー
この時点では、スロットの内容は確かにレンダリングされますが、順序が正しくありません。そしてすべての上位コンポーネントが最後までレンダリングされます。 。実際、Vue は名前付きスロットを処理するときにスコープ要素を考慮します。まず、Vue はテンプレートをレンダリング関数 (render) にコンパイルします。たとえば、次のテンプレート:
1 2 3 | <div>
<p slot= "slot1" >Base slot</p>
</div>
|
ログイン後にコピー
は次のレンダリング関数にコンパイルされます。 :
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 に加えて、テンプレートには次のようなコンポーネントもあります:
1 2 3 4 5 6 | <div>
<Base>
<p slot= "slot1" >Base slot</p>
<p> default slot</p>
</Base>
</div>
|
ログイン後にコピー
ログイン後にコピー
そのレンダリング関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 以及传递了多少,传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢?很简单,假如组件的模板如下
1 2 3 4 5 6 | <div>
<Base>
<p slot= "slot1" >Base slot</p>
<p> default slot</p>
</Base>
</div>
|
ログイン後にコピー
ログイン後にコピー
父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到
1 2 3 4 | <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实际引用的还是父组件 所以
1 | console.log(this.vnode.context === this.vnode.componentOptions.children[0].context)
|
ログイン後にコピー
最终导致具名插槽被作为默认插槽,从而渲染不正确。
决办法也很简单,只需要手动设置一下 slot 中 VNode 的 context 值为高阶组件实例即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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]), [])
.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 是一个代理对象
【相关推荐:vuejs视频教程、web前端开发】
以上がvue の上位コンポーネントとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。