首頁 >CMS教程 >&#&按 >Vue.js元件間通訊的設計模式

Vue.js元件間通訊的設計模式

WBOY
WBOY原創
2023-09-02 11:45:111102瀏覽

作為開發人員,我們希望產生可管理且可維護的程式碼,這也更容易除錯和測試。為了實現這一點,我們採用了稱為模式的最佳實踐。模式是經過驗證的演算法和架構,可以幫助我們以高效且可預測的方式完成特定任務。

在本教程中,我們將了解最常見的 Vue.js 元件通訊模式,以及我們應該避免的一些陷阱。我們都知道,在現實生活中,沒有單一的解決方案可以解決所有問題。同樣,在 Vue.js 應用程式開發中,不存在適用於所有程式設計場景的通用模式。每種模式都有其自身的優點和缺點,並且適合特定的用例。

對於 Vue.js 開發人員來說,最重要的是了解所有最常見的模式,這樣我們就可以為給定的專案選擇正確的模式。這將導致正確且高效的組件通訊。

為什麼正確的元件通訊很重要?

當我們使用 Vue.js 等基於元件的框架來建立應用程式時,我們的目標是使應用程式的元件盡可能隔離。這使得它們可重複使用、可維護和可測試。為了使元件可重複使用,我們需要以更抽象和解耦(或鬆散耦合)的形式塑造它,因此,我們可以將其添加到我們的應用程式中或將其刪除,而不會破壞應用程式的功能。

但是,我們無法在應用程式的元件中實現完全隔離和獨立。在某些時候,它們需要相互溝通:交換一些資料、更改應用程式的狀態等。因此,對於我們來說,學習如何正確完成這種通信,同時保持應用程式正常運作、靈活和可擴展非常重要。

#Vue.js 元件通訊概述

在 Vue.js 中,元件之間的通訊主要有兩種:

  1. 直接的親子溝通,基於嚴格的父母與孩子以及孩子與父母的關係。
  2. 跨元件通訊,其中一個元件可以與任何其他元件“對話”,無論它們之間的關係如何。

在以下部分中,我們將探討這兩種類型以及適當的範例。

直接親子溝通

Vue.js 開箱即用支援的元件通訊的標準模型是透過 props 和自訂事件實現的父子模型。在下圖中,您可以直觀地看到該模型的實際效果。

Vue.js元件間通訊的設計模式

如您所見,父級只能與其直接子級通信,子級也只能直接與其父級通信。在此模型中,不可能進行同級或跨組件通訊。

在下面的部分中,我們將採用上圖中的元件,並在一系列實際範例中實現它們。

#親子溝通

假設我們擁有的元件是遊戲的一部分。大多數遊戲都會在介面中的某個位置顯示遊戲得分。想像一下,我們在 Parent A 元件中宣告了一個 score 變量,並且我們希望在 Child A 元件中顯示它。那麼,我們要怎麼做才能做到這一點呢?

為了將資料從父級傳送到子級,Vue.js 使用 props。傳遞屬性需要三個必要步驟:

  1. 在子層級中註冊屬性,如下所示:props: ["score"]
  2. 使用子模板中註冊的屬性,如下所示:<span>分數:{{ Score }}</span>
  3. #將該屬性綁定到 score 變數(在父級模板中),如下所示:<child-a :score="score"></child-a>

讓我們探索一個完整的範例,以便更好地了解實際發生的情況:

// 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 範例

驗證道具

為了簡潔和清楚起見,我使用它們的速記變體來註冊道具。但在實際開發中,建議對 props 進行驗證。這將確保道具將收到正確類型的值。例如,我們的 score 屬性可以這樣驗證:

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

使用 props 時,請確保您了解它們的文字變體和動態變體之間的差異。當我們將prop 綁定到變數時,它是動態的(例如,v-bind:score="score" 或其簡寫:score="score") ,因此,prop 的值會根據變數的值而變化。如果我們只是輸入一個沒有綁定的值,那麼該值將按字面意思解釋,並且結果將是靜態的。在我們的例子中,如果我們寫 score="score",它將顯示 score 而不是 100。這是一個字面上的道具。您應該小心這種細微的差異。

更新子屬性

到目前為止,我們已經成功顯示了遊戲得分,但在某些時候我們需要更新它。讓我們試試這個。

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

我们看一下下图:

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 构建复杂应用程序的专门教程。

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

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

以上是Vue.js元件間通訊的設計模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:增強 WordPress:打造改進的 API 和函式庫下一篇:增強 WordPress:打造改進的 API 和函式庫

相關文章

看更多