Que sont les composants d'ordre supérieur de Vue ?

青灯夜游
Libérer: 2022-12-20 13:24:37
original
2071 Les gens l'ont consulté

Dans Vue, un composant d'ordre supérieur est en fait une fonction d'ordre supérieur, c'est-à-dire une fonction qui renvoie une fonction de composant. Caractéristiques des composants d'ordre supérieur : 1. Il s'agit d'une fonction pure sans effets secondaires, et le composant d'origine ne doit pas être modifié, c'est-à-dire que le composant d'origine ne peut pas être modifié 2. Il ne se soucie pas des données (accessoires) ; passé, et le composant nouvellement généré ne se soucie pas de la source des données 3. Les accessoires reçus doivent être transmis au composant empaqueté, c'est-à-dire que les accessoires du composant d'origine sont directement transmis au composant d'emballage. Les composants de commande peuvent complètement ajouter, supprimer et modifier des accessoires.

Que sont les composants d'ordre supérieur de Vue ?

L'environnement d'exploitation de ce tutoriel : système windows7, version vue3, ordinateur DELL G3.

Introduction aux composants d'ordre supérieur

vue Compréhension des composants d'ordre supérieur, dans React, les composants sont implémentés avec du code réutilisé, tandis que dans Vue, ils sont implémentés avec des mixins, et certains concepts de composants d'ordre supérieur sont également manquants dans Les documents officiels. Parce qu'il est difficile d'implémenter des groupes d'ordre élevé dans Vue, ce n'est pas aussi simple que React. En fait, les mixins dans Vue sont également remplacés par des mixins. Après avoir lu une partie du code source, j'ai une compréhension plus approfondie. de Vue

Le composant dit d'ordre élevé est en fait une fonction de premier ordre, c'est-à-dire une fonction qui renvoie une fonction de composant. Comment l'implémenter dans Vue ? Notez que les composants d'ordre élevé ont les caractéristiques suivantes

高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源
高阶组件(HOC)接收到的 props 应该传递给被包装组件即直接将原组件prop传给包装组件
高阶组件完全可以添加、删除、修改 props
Copier après la connexion

Exemples de composants d'ordre élevé

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>
Copier après la connexion

Les composants Vue ont principalement trois points : les accessoires, les événements et les emplacements. Pour le composant Base Component, il reçoit un prop de type numérique, à savoir test, et déclenche un événement personnalisé. Le nom de l'événement est : Base-click, sans slots. Nous allons utiliser le composant comme ceci :

Maintenant, nous avons besoin que le composant de base imprime une phrase : haha ​​​​à chaque fois qu'il est monté. En même temps, cela peut être une exigence de nombreux composants, donc selon le. méthode mixins, nous pouvons faire ceci, définissez d'abord les mixins

export default consoleMixin {
  mounted () {
    console.log('haha')
  }
}
Copier après la connexion

puis mélangez consoleMixin dans le composant Base :

<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>
Copier après la connexion

Lorsque vous utilisez le composant Base comme celui-ci, un message haha ​​​​sera imprimé une fois chaque montage terminé, mais maintenant nous devons utiliser des composants d'ordre supérieur pour obtenir la même fonction. Fonction, rappelez-vous la définition des composants d'ordre supérieur : recevoir un composant en tant que paramètre et renvoyer un nouveau composant. Ce à quoi nous devons donc penser en ce moment, c'est ce que c'est. un composant dans Vue ? Les composants dans Vue sont des fonctions, mais c'est le résultat final. Par exemple, notre définition de composant dans un composant à fichier unique est en fait un objet d'options ordinaire, comme suit :

export default {
  name: 'Base',
  props: {...},
  mixins: [...]
  methods: {...}
}
Copier après la connexion

N'est-ce pas un pur objet

import Base from './Base.vue'
console.log(Base)
Copier après la connexion

Quoi ? est la base ici ? Il s'agit d'un objet JSON, et lorsque vous l'ajoutez aux composants d'un composant, Vu finira par construire le constructeur de l'instance avec ce paramètre, l'option Par conséquent, le composant dans Vue est une fonction, mais. ce n'est encore qu'une option avant son introduction. Objet, il est donc facile de comprendre qu'un composant dans Vue n'est qu'un objet au début, c'est-à-dire qu'un composant d'ordre supérieur est une fonction qui accepte un objet pur et renvoie un nouvel objet pur

export default function Console (BaseComponent) {
  return {
    template: '<wrapped v-on="$listeners" v-bind="$attrs"/>',
    components: {
      wrapped: BaseComponent
    },
    mounted () {
      console.log('haha')
    }
  }
}
Copier après la connexion

Ici, la console est un composant d'ordre élevé qui accepte un paramètre. BaseComponent est le composant transmis, renvoie un nouveau composant, utilise BaseComponent comme sous-composant du nouveau composant et définit la fonction hook dans monté pour imprimer haha. Nous pouvons faire la même chose que les mixins. Nous n'avons pas modifié le sous-composant Base. Ici, $listeners $attrs transmet en fait de manière transparente les accessoires et les événements. Cela résout-il vraiment parfaitement le problème ? Non, tout d'abord, l'option template ne peut être utilisée que dans la version complète de Vue, pas dans la version runtime, donc à tout le moins nous devrions utiliser la fonction render (render) au lieu du template (template)

Console .js

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
      })
    }
  }
}
Copier après la connexion

Nous avons réécrit le modèle en fonction de rendu. Il semble qu'il n'y ait pas de problème, mais en fait, il y a toujours un problème dans le code ci-dessus, le composant BaseComponent ne peut toujours pas recevoir d'accessoires. passer des attrs dans le deuxième paramètre de la fonction h ? Pourquoi est-ce que je ne peux pas le recevoir ? Bien sûr, vous ne pouvez pas le recevoir. Attrs fait référence à des attributs qui n'ont pas été déclarés comme accessoires, vous devez donc ajouter des paramètres d'accessoires dans la fonction de rendu :

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}
Copier après la connexion

Ensuite, cela ne fonctionne toujours pas. Les accessoires ici sont l'objet d'ordre élevé du composant, mais le composant d'ordre élevé ne déclare pas d'accessoires, nous devons donc déclarer un autre accessoire

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
      })
    }
  }
}
Copier après la connexion

ok Un composant d'ordre élevé similaire est terminé, mais il peut toujours l'être. terminé. Nous implémentons uniquement la transmission transparente des accessoires et la transmission transparente des événements emmmmm, il ne reste que le slot. Nous modifions le composant Base pour ajouter un slot nommé et un slot par défaut 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>
Copier après la connexion

Le résultat de l'exécution ici est que le. les slots dans wrapBase ont disparu, nous devons donc changer le composant de haut niveau

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 函数的第三个参数
    }
  }
}
Copier après la connexion

À ce stade, le contenu du slot est effectivement rendu, mais l'ordre n'est pas correct et tous les composants de haut niveau sont rendus jusqu'à la fin. . En fait, Vue prendra en compte les facteurs de portée lors du traitement des emplacements nommés. Tout d'abord, Vue compilera le modèle dans une fonction de rendu (render). Par exemple, le modèle suivant :

<div>
  <p slot="slot1">Base slot</p>
</div>
Copier après la connexion

sera compilé dans la fonction de rendu suivante :

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")
    ])
  ])
}
Copier après la connexion

. Observez la fonction de rendu ci-dessus. Nous avons constaté que le DOM ordinaire crée le VNode correspondant via la fonction _c. Maintenant, nous modifions le modèle En plus du DOM ordinaire, le modèle comporte également les composants suivants :

<div>
  <Base>
    <p slot="slot1">Base slot</p>
    <p>default slot</p>
  </Base>
</div>
Copier après la connexion
Copier après la connexion

sa fonction de rendu

.
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")])
      ])
    ],
  )
}
Copier après la connexion

我们发现无论是普通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>
Copier après la connexion
Copier après la connexion

父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到

<Base>
    <p slot="slot1">Base slot</p>
    <p>default slot</p>
  </Base>
Copier après la connexion

如果能够通过父级拿到这段模板对应的 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
Copier après la connexion

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

决办法也很简单,只需要手动设置一下 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)
    }
  }
}
Copier après la connexion

说明白就是强制把slot的归属权给高阶组件 而不是 父组件 通过当前实例 _self 属性访问当实例本身,而不是直接使用 this,因为 this 是一个代理对象

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal