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

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

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

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

#このチュートリアルの動作環境: Windows7 システム、vue3 バージョン、DELL G3 コンピューター。

高次コンポーネントの概要

vue 高次コンポーネントの理解。React ではコンポーネントは再利用されたコードで実装されますが、Vue ではミックスインで実装されます。また、Vue で高次コンポーネントを実装するのは難しく、React ほど単純ではないため、公式ドキュメントには高次コンポーネントの概念がいくつか欠けています。実際、Vue のミックスインもミックスインに置き換えられます。ソースコードを見ると、Vue についての理解が深まります理解

いわゆる高階コンポーネントは、実際には高階関数、つまりコンポーネント関数を返す関数です。 Vueで?上位コンポーネントには次の特性があることに注意してください。

高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源
高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件
高阶组件完全可以添加、删除、修改 props
ログイン後にコピー
上位コンポーネントの例

Base.vue

<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

export default consoleMixin {
  mounted () {
    console.log('haha')
  }
}
ログイン後にコピー
を定義し、次に Base コンポーネントで consoleMixin をミックスします。

<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 のコンポーネントは関数ですが、それは最終結果です。たとえば、単一ファイル コンポーネントのコンポーネント定義は、実際には次のような通常のオプション オブジェクトです:

export default {
  name: 'Base',
  props: {...},
  mixins: [...]
  methods: {...}
}
ログイン後にコピー
これは純粋なオブジェクトではありませんか?

import Base from './Base.vue'
console.log(Base)
ログイン後にコピー
Base とは何ですか? これは JSON オブジェクトです。これをコンポーネントのコンポーネントに追加すると、Vu は最終的にこのパラメーター (つまりオプション) を使用してインスタンスのコンストラクターを構築します。 Vue のコンポーネントは関数ですが、導入前はまだ単なるオプション オブジェクトであるため、Vue のコンポーネントは最初は単なるオブジェクト、つまり上位コンポーネントは関数であることが容易に理解できます。純粋なオブジェクトを受け入れ、新しい純粋なオブジェクトを返します

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

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(&#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 サイトの他の関連記事を参照してください。

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