ホームページ > ウェブフロントエンド > フロントエンドQ&A > vue の上位コンポーネントとは何ですか?

vue の上位コンポーネントとは何ですか?

青灯夜游
リリース: 2022-12-20 13:24:37
オリジナル
2203 人が閲覧しました

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

vue の上位コンポーネントとは何ですか?

#このチュートリアルの動作環境: 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) {

  

      // 将 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) にコンパイルします。たとえば、次のテンプレート:

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) // false

ログイン後にコピー

最终导致具名插槽被作为默认插槽,从而渲染不正确。

决办法也很简单,只需要手动设置一下 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(&#39;haha&#39;)

    },

    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 是一个代理对象

【相关推荐:vuejs视频教程web前端开发

以上がvue の上位コンポーネントとは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート