Maison  >  Article  >  Tutoriel CMS  >  Modèles de conception pour la communication entre les composants Vue.js

Modèles de conception pour la communication entre les composants Vue.js

WBOY
WBOYoriginal
2023-09-02 11:45:111078parcourir

En tant que développeurs, nous souhaitons produire un code gérable et maintenable, qui est également plus facile à déboguer et à tester. Pour y parvenir, nous utilisons les meilleures pratiques appelées modèles. Les modèles sont des algorithmes et des architectures éprouvés qui nous aident à accomplir des tâches spécifiques de manière efficace et prévisible.

Dans ce didacticiel, nous examinerons les modèles de communication des composants Vue.js les plus courants, ainsi que certains pièges que nous devrions éviter. Nous savons tous que dans la vraie vie, il n’existe pas de solution unique à tous les problèmes. De même, dans le développement d’applications Vue.js, il n’existe pas de modèle universel s’appliquant à tous les scénarios de programmation. Chaque mode présente ses propres avantages et inconvénients et convient à des cas d'utilisation spécifiques.

La chose la plus importante pour les développeurs Vue.js est de comprendre tous les modèles les plus courants afin de pouvoir choisir celui qui convient à un projet donné. Il en résulte une communication correcte et efficace des composants.

Pourquoi une communication correcte entre les composants est-elle importante ?

Lorsque nous construisons une application à l'aide d'un framework basé sur des composants comme Vue.js, notre objectif est de garder les composants de l'application aussi isolés que possible. Cela les rend réutilisables, maintenables et testables. Pour rendre un composant réutilisable, nous devons le façonner sous une forme plus abstraite et découplée (ou faiblement couplée), afin de pouvoir l'ajouter à notre application ou le supprimer sans interrompre les fonctionnalités de l'application.

Cependant, nous ne pouvons pas parvenir à une isolation et une indépendance complètes entre les composants de l'application. A un moment donné, ils ont besoin de communiquer entre eux : échanger des données, changer l'état de l'application, etc. Il est donc important pour nous d’apprendre à effectuer correctement cette communication tout en gardant l’application fonctionnelle, flexible et évolutive.

Présentation de la communication des composants Vue.js

Dans Vue.js, il existe deux principaux types de communication entre les composants :

  1. Communication directe parent-enfant, basée sur des relations strictes parent-enfant et enfant-parent.
  2. Communication inter-composants, où un composant peut « parler » à n'importe quel autre composant, quelle que soit leur relation.

Dans les sections suivantes, nous explorerons les deux types avec des exemples appropriés.

Communication directe parent-enfant

Le modèle standard de communication entre composants pris en charge par Vue.js est le modèle parent-enfant implémenté via des accessoires et des événements personnalisés. Dans l'image ci-dessous, vous pouvez voir visuellement ce modèle en action.

Modèles de conception pour la communication entre les composants Vue.js

Comme vous pouvez le constater, un parent ne peut communiquer qu'avec ses enfants directs, et un enfant ne peut communiquer directement qu'avec son parent. Dans ce modèle, la communication entre pairs ou entre composants n'est pas possible.

Dans les sections suivantes, nous prendrons les composants de l'image ci-dessus et les implémenterons dans une série d'exemples pratiques.

Communication parent-enfant

Supposons que le composant dont nous disposons fasse partie d'un jeu. La plupart des jeux affichent le score du jeu quelque part dans l'interface. Imaginez que nous déclarons une variable dans le composant Parent Ascore et que nous souhaitons l'afficher dans le composant Child A . Alors, comment pouvons-nous faire cela ?

Pour envoyer des données du parent à l'enfant, Vue.js utilise des accessoires. Passer des propriétés nécessite trois étapes nécessaires :

  1. Enregistrez la propriété de l'enfant comme ceci : props: ["score"]
  2. Utilisez les propriétés enregistrées dans le modèle enfant comme ceci : <span>分数:{{ Score }}</span>
  3. Liez la propriété à la variable score 变量(在父级模板中),如下所示:<child-a :score="score"></child-a> (dans le modèle parent) comme ceci : <child-a :score="score"></child-a>

Explorons un exemple complet pour mieux comprendre ce qui se passe réellement :

// HTML part

<div id="app">
  <grand-parent/>
</div>

// JavaScript part

Vue.component('ChildB',{
  template:`
    <div id="child-b">
      <h2>Child B</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

`, }) Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ score }} // 2.Using
`, props: ["score"] // 1.Registering }) Vue.component('ParentB',{ template:`

Parent B

data {{ this.$data }}

`, }) Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
<child-a :score="score"></child-a> // 3.Binding
`, data() { return { score: 100 } } }) Vue.component('GrandParent',{ template:`

Grand Parent

data {{ this.$data }}

`, }) new Vue ({ el: '#app' })

Exemple CodePen

Accessoires de vérification

Par souci de concision et de clarté, j'enregistre les accessoires en utilisant leurs variations sténographiques. Mais dans le développement réel, il est recommandé de vérifier les accessoires. Cela garantira que l’accessoire recevra le bon type de valeur. Par exemple, notre attribut score peut être vérifié comme ceci :

props: {
    // Simple type validation
    score: Number,
    // or Complex type validation
    score: {
      type: Number,
      default: 100,
      required: true
    }
  }

Lorsque vous travaillez avec des accessoires, assurez-vous de bien comprendre la différence entre leurs variantes littérales et dynamiques. Lorsque nous lions un accessoire à une variable, il est dynamique (par exemple, v-bind:score="score" 或其简写 :score="score") ,因此,prop 的值将根据变量的值而变化。如果我们只是输入一个没有绑定的值,那么该值将按字面意思解释,并且结果将是静态的。在我们的例子中,如果我们编写 score="score"), il affichera score au lieu de 100. Il s'agit d'un accessoire littéral. Vous devez faire attention à cette différence subtile.

Mettre à jour les sous-attributs

Jusqu'à présent, nous avons affiché avec succès le score du jeu, mais à un moment donné, nous devrons le mettre à jour. Essayons ceci.

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}


Score: {{ score }} `, props: ["score"], methods: { changeScore() { this.score = 200; } } })

我们创建了一个 changeScore() 方法,该方法应该在我们按下更改分数按钮后更新分数。当我们这样做时,似乎分数已正确更新,但我们在控制台中收到以下 Vue 警告:

[Vue warn]:避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,根据 prop 的值使用数据或计算属性。正在变异的道具:“score”

正如你所看到的,Vue 告诉我们,如果父级重新渲染,该 prop 将被覆盖。让我们通过使用内置 $forceUpdate() 方法模拟此类行为来测试这一点:

Vue.component('ParentA',{
  template:`
    <div id="parent-a">
      <h2>Parent A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

<child-a :score="score"></child-a> `, data() { return { score: 100 } }, methods: { reRender() { this.$forceUpdate(); } } })

CodePen 示例

现在,当我们更改分数,然后按重新渲染父级按钮时,我们可以看到分数从父级返回到其初始值。所以 Vue 说的是实话!

但请记住,数组和对象影响它们的父对象,因为它们不是被复制,而是通过引用传递。

因此,当我们需要改变子级中的 prop 时,有两种方法可以解决这种重新渲染的副作用。

使用本地数据属性改变 Prop

第一种方法是将 score 属性转换为本地数据属性 (localScore),我们可以在 changeScore( )方法和模板中:

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ localScore }} `, props: ["score"], data() { return { localScore: this.score } }, methods: { changeScore() { this.localScore = 200; } } })

CodePen 示例

现在,如果我们在更改分数后再次按渲染父项按钮,我们会看到这次分数保持不变。

使用计算属性改变 Prop

第二种方法是在计算属性中使用 score 属性,它将被转换为新值:

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ doubleScore }} `, props: ["score"], computed: { doubleScore() { return this.score * 2 } } })

CodePen 示例

在这里,我们创建了一个计算的 doubleScore(),它将父级的 score 乘以 2,然后将结果显示在模板中。显然,按渲染父级按钮不会产生任何副作用。

孩子与家长的沟通

现在,让我们看看组件如何以相反的方式进行通信。

我们刚刚了解了如何改变子组件中的某个 prop,但是如果我们需要在多个子组件中使用该 prop 该怎么办?在这种情况下,我们需要从父级中的源中改变 prop,这样所有使用该 prop 的组件都将被正确更新。为了满足这一要求,Vue 引入了自定义事件。

这里的原则是,我们通知父级我们想要做的更改,父级执行该更改,并且该更改通过传递的 prop 反映。以下是此操作的必要步骤:

  1. 在子进程中,我们发出一个事件来描述我们想要执行的更改,如下所示:this.$emit('updatingScore', 200)
  2. 在父级中,我们为发出的事件注册一个事件监听器,如下所示:@updatingScore="updateScore"
  3. 当事件发出时,分配的方法将更新属性,如下所示:this.score = newValue

让我们探索一个完整的示例,以更好地理解这是如何发生的:

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ score }} `, props: ["score"], methods: { changeScore() { this.$emit('updatingScore', 200) // 1. Emitting } } }) ... Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
<child-a :score="score"></child-a> // 2.Registering
`, data() { return { score: 100 } }, methods: { reRender() { this.$forceUpdate() }, updateScore(newValue) { this.score = newValue // 3.Updating } } })

CodePen 示例

我们使用内置的 $emit() 方法来发出事件。该方法有两个参数。第一个参数是我们要发出的事件,第二个参数是新值。

.sync 修饰符

Vue 提供了 .sync 修饰符,其工作原理类似,在某些情况下我们可能希望将其用作快捷方式。在这种情况下,我们以稍微不同的方式使用 $emit() 方法。作为事件参数,我们将 update:score 如下所示:this.$emit('update:score', 200)。然后,当我们绑定 score 属性时,我们添加 .sync 修饰符,如下所示: <child-a :score.sync="score "></child-a>.在 Parent A 组件中,我们删除了 updateScore() 方法和事件注册 (@updatingScore="updateScore"),因为它们不再需要了。

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ score }} `, props: ["score"], methods: { changeScore() { this.$emit('update:score', 200) } } }) ... Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
`, data() { return { score: 100 } }, methods: { reRender() { this.$forceUpdate() } } })

CodePen 示例

为什么不使用 this.$parentthis.$children 进行直接父子通信?

Vue 提供了两种 API 方法,使我们可以直接访问父组件和子组件:this.$parentthis.$children。一开始,可能很想将它们用作道具和事件的更快、更容易的替代品,但我们不应该这样做。这被认为是一种不好的做法或反模式,因为它在父组件和子组件之间形成了紧密耦合。后者会导致组件不灵活且易于损坏,难以调试和推理。这些 API 方法很少使用,根据经验,我们应该避免或谨慎使用它们。

双向组件通信

道具和事件是单向的。道具下降,事件上升。但是通过一起使用 props 和 events,我们可以在组件树上有效地进行上下通信,从而实现双向数据绑定。这实际上是 v-model 指令在内部执行的操作。

跨组件通信

随着我们的应用程序复杂性的增加,亲子沟通模式很快就会变得不方便且不切实际。 props-events 系统的问题在于它直接工作,并且与组件树紧密绑定。与原生事件相比,Vue 事件不会冒泡,这就是为什么我们需要重复发出它们直到达到目标。结果,我们的代码因过多的事件侦听器和发射器而变得臃肿。因此,在更复杂的应用程序中,我们应该考虑使用跨组件通信模式。

我们看一下下图:

Modèles de conception pour la communication entre les composants Vue.js

如您所见,在这种任意类型的通信中,每个组件都可以发送和/或接收数据来自任何其他组件,无需中间步骤和中间组件。

在以下部分中,我们将探讨跨组件通信的最常见实现。

全局事件总线

全局事件总线是一个 Vue 实例,我们用它来发出和监听事件。让我们在实践中看看它。

const eventBus = new Vue () // 1.Declaring

...

Vue.component('ChildA',{
  template:`
    <div id="child-a">
      <h2>Child A</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ score }} `, props: ["score"], methods: { changeScore() { eventBus.$emit('updatingScore', 200) // 2.Emitting } } }) ... Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
<child-a :score="score"></child-a> `, data() { return { score: 100 } }, created () { eventBus.$on('updatingScore', this.updateScore) // 3.Listening }, methods: { reRender() { this.$forceUpdate() }, updateScore(newValue) { this.score = newValue } } })

CodePen 示例

以下是创建和使用事件总线的步骤:

  1. 将我们的事件总线声明为一个新的 Vue 实例,如下所示:const eventBus = new Vue ()
  2. 从源组件发出事件,如下所示:eventBus.$emit('updatingScore', 200)
  3. 监听目标组件中发出的事件,如下所示:eventBus.$on('updatingScore', this.updateScore)

在上面的代码示例中,我们从子级中删除 @updatingScore="updateScore",并使用 created() 生命周期挂钩来监听对于 updatingScore 事件。当事件发出时,将执行 updateScore() 方法。我们还可以将更新方法作为匿名函数传递:

created () {
  eventBus.$on('updatingScore', newValue => {this.score = newValue})
}

全局事件总线模式可以在一定程度上解决事件膨胀问题,但它会带来其他问题。可以从应用程序的任何部分更改应用程序的数据,而不会留下痕迹。这使得应用程序更难调试和测试。

对于更复杂的应用程序,事情可能很快就会失控,我们应该考虑专用的状态管理模式,例如 Vuex,它将为我们提供更细粒度的控制、更好的代码结构和组织以及有用的更改跟踪和调试功能。

Vuex

Vuex 是一个状态管理库,专为构建复杂且可扩展的 Vue.js 应用程序而定制。使用 Vuex 编写的代码更加冗长,但从长远来看这是值得的。它对应用程序中的所有组件使用集中存储,使我们的应用程序更有组织、透明且易于跟踪和调试。商店是完全响应式的,因此我们所做的更改会立即反映出来。

在这里,我将向您简要解释什么是 Vuex,并提供一个上下文示例。如果您想更深入地了解 Vuex,我建议您看一下我关于使用 Vuex 构建复杂应用程序的专门教程。

现在让我们研究一下下面的图表:

Modèles de conception pour la communication entre les composants Vue.js

如您所见,Vuex 应用程序由四个不同的部分组成:

让我们创建一个简单的商店,看看这一切是如何运作的。

const store = new Vuex.Store({
  state: {
    score: 100
  },
  mutations: {
    incrementScore (state, payload) {
      state.score += payload
    }
  },
  getters: {
    score (state){
      return state.score
    }
  },
  actions: {
    incrementScoreAsync: ({commit}, payload) => {
      setTimeout(() => {
        commit('incrementScore', 100)
      }, payload)
    }
  }
})

Vue.component('ChildB',{
  template:`
    <div id="child-b">
      <h2>Child B</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

`, }) Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ score }} `, computed: { score () { return store.getters.score; } }, methods: { changeScore (){ store.commit('incrementScore', 100) } } }) Vue.component('ParentB',{ template:`

Parent B

data {{ this.$data }}

Score: {{ score }}
`, computed: { score () { return store.getters.score; } }, methods: { changeScore (){ store.dispatch('incrementScoreAsync', 3000); } } }) Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
`, }) Vue.component('GrandParent',{ template:`

Grand Parent

data {{ this.$data }}

`, }) new Vue ({ el: '#app', })

CodePen 示例

在商店里,我们有以下产品:

在 Vue 实例中,我们不使用 props,而是使用计算属性通过 getter 来获取分数值。然后,为了更改分数,在 Child A 组件中,我们使用突变 store.commit('incrementScore', 100)。在Parent B组件中,我们使用操作 store.dispatch('incrementScoreAsync', 3000)

依赖注入

在结束之前,让我们探讨另一种模式。它的用例主要用于共享组件库和插件,但为了完整性值得一提。

依赖注入允许我们通过 provide 属性定义服务,该属性应该是一个对象或返回对象的函数,并使其可供组件的所有后代使用,而不仅仅是其组件直接孩子。然后,我们可以通过 inject 属性使用该服务。

让我们看看实际效果:

Vue.component('ChildB',{
  template:`
    <div id="child-b">
      <h2>Child B</h2>
      <pre class="brush:php;toolbar:false">data {{ this.$data }}

Score: {{ score }} `, inject: ['score'] }) Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ score }} `, inject: ['score'], }) Vue.component('ParentB',{ template:`

Parent B

data {{ this.$data }}

Score: {{ score }}
`, inject: ['score'] }) Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ score }} `, inject: ['score'], methods: { reRender() { this.$forceUpdate() } } }) Vue.component('GrandParent',{ template:`

Grand Parent

data {{ this.$data }}

`, provide: function () { return { score: 100 } } }) new Vue ({ el: '#app', })

CodePen 示例

通过使用祖父组件中的 provide 选项,我们将 score 变量设置为可供其所有后代使用。他们每个人都可以通过声明 inject: ['score'] 属性来访问它。而且,正如您所看到的,分数显示在所有组件中。

注意:依赖注入创建的绑定不是反应性的。因此,如果我们希望提供程序组件中所做的更改反映在其后代中,我们必须将一个对象分配给数据属性并在提供的服务中使用该对象。

为什么不使用 this.$root 进行跨组件通信?

我们不应该使用 this.$root 的原因与之前描述的 this.$parentthis.$children 的原因类似——它创建了太多的依赖关系。必须避免依赖任何这些方法进行组件通信。

如何选择正确的模式

所以你已经了解了组件通信的所有常用方法。但您如何决定哪一个最适合您的场景呢?

选择正确的模式取决于您参与的项目或您想要构建的应用程序。这取决于您的应用程序的复杂性和类型。让我们探讨一下最常见的场景:

最后一件事。您不必仅仅因为别人告诉您这样做就需要使用任何已探索的模式。您可以自由选择和使用您想要的任何模式,只要您设法保持应用程序正常运行并且易于维护和扩展。

结论

在本教程中,我们学习了最常见的 Vue.js 组件通信模式。我们了解了如何在实践中实施它们以及如何选择最适合我们项目的正确方案。这将确保我们构建的应用程序使用正确类型的组件通信,使其完全正常工作、可维护、可测试和可扩展。

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!

Déclaration:
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
Article précédent:Améliorez WordPress : créez des API et des bibliothèques amélioréesArticle suivant:Améliorez WordPress : créez des API et des bibliothèques améliorées

Articles Liés

Voir plus