Heim  >  Artikel  >  CMS-Tutorial  >  Entwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten

Entwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten

WBOY
WBOYOriginal
2023-09-02 11:45:11961Durchsuche

Als Entwickler möchten wir verwaltbaren und wartbaren Code produzieren, der sich auch einfacher debuggen und testen lässt. Um dies zu erreichen, verwenden wir Best Practices, sogenannte Muster. Muster sind bewährte Algorithmen und Architekturen, die uns dabei helfen, bestimmte Aufgaben effizient und vorhersehbar zu erledigen.

In diesem Tutorial werfen wir einen Blick auf die häufigsten Kommunikationsmuster von Vue.js-Komponenten sowie auf einige Fallstricke, die wir vermeiden sollten. Wir alle wissen, dass es im wirklichen Leben nicht für jedes Problem eine einzige Lösung gibt. Ebenso gibt es bei der Anwendungsentwicklung von Vue.js kein universelles Muster, das für alle Programmierszenarien gilt. Jeder Modus hat seine eigenen Vor- und Nachteile und ist für bestimmte Anwendungsfälle geeignet.

Das Wichtigste für Vue.js-Entwickler ist, alle gängigsten Muster zu verstehen, damit wir das richtige für ein bestimmtes Projekt auswählen können. Dies führt zu einer korrekten und effizienten Komponentenkommunikation.

Warum ist die richtige Komponentenkommunikation wichtig?

Wenn wir eine App mit einem komponentenbasierten Framework wie Vue.js erstellen, ist es unser Ziel, die Komponenten der App so isoliert wie möglich zu halten. Dadurch sind sie wiederverwendbar, wartbar und testbar. Um eine Komponente wiederverwendbar zu machen, müssen wir sie in eine abstraktere und entkoppelte (oder lose gekoppelte) Form bringen, damit wir sie zu unserer Anwendung hinzufügen oder entfernen können, ohne die Funktionalität der Anwendung zu beeinträchtigen.

Allerdings können wir keine vollständige Isolation und Unabhängigkeit zwischen den Komponenten der Anwendung erreichen. Irgendwann müssen sie miteinander kommunizieren: Daten austauschen, den Status der Anwendung ändern usw. Daher ist es für uns wichtig zu lernen, wie man diese Kommunikation richtig macht und gleichzeitig die Funktionalität, Flexibilität und Skalierbarkeit der Anwendung beibehält.

Vue.js-Komponentenkommunikationsübersicht

In Vue.js gibt es zwei Haupttypen der Kommunikation zwischen Komponenten:

  1. Direkte Eltern-Kind-Kommunikation, basierend auf strengen Eltern-Kind- und Kind-Eltern-Beziehungen.
  2. Komponentenübergreifende Kommunikation, bei der eine Komponente mit jeder anderen Komponente „sprechen“ kann, unabhängig von ihrer Beziehung.

In den folgenden Abschnitten werden wir beide Typen anhand geeigneter Beispiele untersuchen.

Direkte Eltern-Kind-Kommunikation

Das Standardmodell für die Komponentenkommunikation, das Vue.js standardmäßig unterstützt, ist das Eltern-Kind-Modell, das durch Requisiten und benutzerdefinierte Ereignisse implementiert wird. Im Bild unten können Sie dieses Modell visuell in Aktion sehen.

Entwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten

Wie Sie sehen, kann ein Elternteil nur mit seinen direkten Kindern kommunizieren, und ein Kind kann nur direkt mit seinem Elternteil kommunizieren. In diesem Modell ist keine Peer- oder komponentenübergreifende Kommunikation möglich.

In den folgenden Abschnitten werden wir die Komponenten im Bild oben nehmen und sie in einer Reihe praktischer Beispiele umsetzen.

Eltern-Kind-Kommunikation

Angenommen, die Komponente, die wir haben, ist Teil eines Spiels. Bei den meisten Spielen wird der Spielstand irgendwo in der Benutzeroberfläche angezeigt. Stellen Sie sich vor, wir deklarieren eine Variable in der Komponente Parent Ascore und möchten sie in der Komponente Child A anzeigen. Wie können wir das also tun?

Um Daten vom Elternteil zum Kind zu senden, verwendet Vue.js Requisiten. Die Übergabe von Attributen erfordert drei notwendige Schritte:

  1. Registrieren Sie die Eigenschaften des Kindes wie folgt: props: ["score"]
  2. Verwenden Sie die in der untergeordneten Vorlage registrierten Attribute wie folgt: <span>分数:{{ Score }}</span>
  3. Binden Sie die Eigenschaft wie folgt an die Variable score 变量(在父级模板中),如下所示:<child-a :score="score"></child-a> (in der übergeordneten Vorlage): <child-a :score="score"></child-a>

Lassen Sie uns ein vollständiges Beispiel untersuchen, um besser zu verstehen, was tatsächlich passiert:

// 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' })

CodePen-Beispiel

Verifizierungs-Requisiten

Der Kürze und Klarheit halber registriere ich Requisiten mit ihren Kurzschriftvarianten. In der tatsächlichen Entwicklung wird jedoch empfohlen, Requisiten zu überprüfen. Dadurch wird sichergestellt, dass die Requisite den richtigen Werttyp erhält. Unser score-Attribut kann beispielsweise so verifiziert werden:

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

Stellen Sie bei der Arbeit mit Requisiten sicher, dass Sie den Unterschied zwischen ihrer wörtlichen und dynamischen Variante verstehen. Wenn wir eine Requisite an eine Variable binden (z. B. v-bind:score="score" 或其简写 :score="score") ,因此,prop 的值将根据变量的值而变化。如果我们只是输入一个没有绑定的值,那么该值将按字面意思解释,并且结果将是静态的。在我们的例子中,如果我们编写 score="score"), wird Score anstelle von 100 angezeigt. Dies ist eine buchstäbliche Requisite

Unterattribute aktualisieren

Bisher haben wir den Spielstand erfolgreich angezeigt, aber irgendwann müssen wir ihn aktualisieren. Versuchen wir es mal.

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 事件不会冒泡,这就是为什么我们需要重复发出它们直到达到目标。结果,我们的代码因过多的事件侦听器和发射器而变得臃肿。因此,在更复杂的应用程序中,我们应该考虑使用跨组件通信模式。

我们看一下下图:

Entwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten

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

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

全局事件总线

全局事件总线是一个 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 构建复杂应用程序的专门教程。

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

Entwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten

如您所见,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 组件通信模式。我们了解了如何在实践中实施它们以及如何选择最适合我们项目的正确方案。这将确保我们构建的应用程序使用正确类型的组件通信,使其完全正常工作、可维护、可测试和可扩展。

Das obige ist der detaillierte Inhalt vonEntwurfsmuster für die Kommunikation zwischen Vue.js-Komponenten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:Verbessern Sie WordPress: Erstellen Sie verbesserte APIs und BibliothekenNächster Artikel:Verbessern Sie WordPress: Erstellen Sie verbesserte APIs und Bibliotheken

In Verbindung stehende Artikel

Mehr sehen