Design patterns for communication between Vue.js components
Sep 02, 2023 am 11:45 AMAs developers, we want to produce code that is manageable and maintainable, which is also easier to debug and test. To achieve this, we employ best practices called patterns. Patterns are proven algorithms and architectures that help us accomplish specific tasks in an efficient and predictable way.
In this tutorial, we'll take a look at the most common Vue.js component communication patterns, as well as some pitfalls we should avoid. We all know that in real life, there is no single solution to every problem. Likewise, in Vue.js application development, there is no universal pattern that applies to all programming scenarios. Each mode has its own advantages and disadvantages and is suitable for specific use cases.
The most important thing for Vue.js developers is to understand all the most common patterns so that we can choose the right one for a given project. This results in correct and efficient component communication.
Why is correct component communication important?
When we build an application using a component-based framework such as Vue.js, our goal is to keep the components of the application as isolated as possible. This makes them reusable, maintainable and testable. To make a component reusable, we need to shape it in a more abstract and decoupled (or loosely coupled) form, so we can add it to our application or remove it without breaking the functionality of the application .
However, we cannot achieve complete isolation and independence among the components of the application. At some point, they need to communicate with each other: exchange some data, change the state of the application, etc. Therefore, it is important for us to learn how to do this communication correctly while keeping the application functional, flexible, and scalable.
Vue.js component communication overview
In Vue.js, there are two main types of communication between components:
- Direct parent-child communication, based on strict parent-child and child-parent relationships.
- Cross-component communication, where one component can "talk" to any other component, regardless of their relationship.
In the following sections we will explore both types along with appropriate examples.
Direct parent-child communication
The standard model for component communication that Vue.js supports out of the box is the parent-child model implemented through props and custom events. In the image below you can visually see this model in action.
As you can see, a parent can only communicate with its direct children, and a child can only communicate directly with its parent. In this model, peer or cross-component communication is not possible.
In the following sections, we will take the components in the picture above and implement them in a series of practical examples.
Parent-child communication
Let's say the component we have is part of a game. Most games display the game score somewhere in the interface. Imagine that we declare a score variable in the Parent A
component, and we want to display it in the Child A component. So, how can we do this?
To send data from parent to child, Vue.js uses props. Passing attributes requires three necessary steps:
- Register the properties in the child as follows:
props: ["score"]
- Use the attributes registered in the subtemplate as follows:
<span>Score: {{ Score }}</span>
- Bind this property to the
score
variable (in the parent template) like this:<child-a :score="score"></child-a>
Let's explore a complete example to better understand what is actually happening:
// 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 }}
Score: {{ score }} // 2.Using
Parent B
data {{ this.$data }}
Grand Parent
data {{ this.$data }}
CodePen Example
Verification props
For the sake of brevity and clarity, I register props using their shorthand variations. But in actual development, it is recommended to verify props. This will ensure that the prop will receive the correct type of value. For example, our score
property can be verified like this:
props: { // Simple type validation score: Number, // or Complex type validation score: { type: Number, default: 100, required: true } }
When working with props, make sure you understand the difference between their literal and dynamic variants. When we bind a prop to a variable, it is dynamic (for example, v-bind:score="score"
or its abbreviation :score="score"
), so , the value of prop will change based on the value of the variable. If we just enter a value without a binding, the value will be interpreted literally and the result will be static. In our example, if we write score="score"
, it will display score instead of 100. This is a literal prop. You should be careful about this subtle difference.
Update sub-attributes
So far we have successfully displayed the game score, but at some point we need to update it. Let's try this.
Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">Child A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ score }}
我們創(chuàng)建了一個(gè) changeScore()
方法,該方法應(yīng)該在我們按下更改分?jǐn)?shù)按鈕后更新分?jǐn)?shù)。當(dāng)我們這樣做時(shí),似乎分?jǐn)?shù)已正確更新,但我們?cè)诳刂婆_(tái)中收到以下 Vue 警告:
[Vue warn]:避免直接改變 prop,因?yàn)橹灰附M件重新渲染,該值就會(huì)被覆蓋。相反,根據(jù) prop 的值使用數(shù)據(jù)或計(jì)算屬性。正在變異的道具:“score”
正如你所看到的,Vue 告訴我們,如果父級(jí)重新渲染,該 prop 將被覆蓋。讓我們通過使用內(nèi)置 $forceUpdate()
方法模擬此類行為來測(cè)試這一點(diǎn):
Vue.component('ParentA',{ template:` <div id="parent-a"> <h2 id="Parent-A">Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
現(xiàn)在,當(dāng)我們更改分?jǐn)?shù),然后按重新渲染父級(jí)按鈕時(shí),我們可以看到分?jǐn)?shù)從父級(jí)返回到其初始值。所以 Vue 說的是實(shí)話!
但請(qǐng)記住,數(shù)組和對(duì)象將影響它們的父對(duì)象,因?yàn)樗鼈儾皇潜粡?fù)制,而是通過引用傳遞。
因此,當(dāng)我們需要改變子級(jí)中的 prop 時(shí),有兩種方法可以解決這種重新渲染的副作用。
使用本地?cái)?shù)據(jù)屬性改變 Prop
第一種方法是將 score
屬性轉(zhuǎn)換為本地?cái)?shù)據(jù)屬性 (localScore
),我們可以在 changeScore( )
方法和模板中:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">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 示例
現(xiàn)在,如果我們?cè)诟姆謹(jǐn)?shù)后再次按渲染父項(xiàng)按鈕,我們會(huì)看到這次分?jǐn)?shù)保持不變。
使用計(jì)算屬性改變 Prop
第二種方法是在計(jì)算屬性中使用 score
屬性,它將被轉(zhuǎn)換為新值:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">Child A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
Score: {{ doubleScore }} `, props: ["score"], computed: { doubleScore() { return this.score * 2 } } })
CodePen 示例
在這里,我們創(chuàng)建了一個(gè)計(jì)算的 doubleScore()
,它將父級(jí)的 score
乘以 2,然后將結(jié)果顯示在模板中。顯然,按渲染父級(jí)按鈕不會(huì)產(chǎn)生任何副作用。
孩子與家長(zhǎng)的溝通
現(xiàn)在,讓我們看看組件如何以相反的方式進(jìn)行通信。
我們剛剛了解了如何改變子組件中的某個(gè) prop,但是如果我們需要在多個(gè)子組件中使用該 prop 該怎么辦?在這種情況下,我們需要從父級(jí)中的源中改變 prop,這樣所有使用該 prop 的組件都將被正確更新。為了滿足這一要求,Vue 引入了自定義事件。
這里的原則是,我們通知父級(jí)我們想要做的更改,父級(jí)執(zhí)行該更改,并且該更改通過傳遞的 prop 反映。以下是此操作的必要步驟:
- 在子進(jìn)程中,我們發(fā)出一個(gè)事件來描述我們想要執(zhí)行的更改,如下所示:
this.$emit('updatingScore', 200)
- 在父級(jí)中,我們?yōu)榘l(fā)出的事件注冊(cè)一個(gè)事件監(jiān)聽器,如下所示:
@updatingScore="updateScore"
- 當(dāng)事件發(fā)出時(shí),分配的方法將更新屬性,如下所示:
this.score = newValue
讓我們探索一個(gè)完整的示例,以更好地理解這是如何發(fā)生的:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">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 id="Parent-A">Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
我們使用內(nèi)置的 $emit()
方法來發(fā)出事件。該方法有兩個(gè)參數(shù)。第一個(gè)參數(shù)是我們要發(fā)出的事件,第二個(gè)參數(shù)是新值。
.sync
修飾符
Vue 提供了 .sync
修飾符,其工作原理類似,在某些情況下我們可能希望將其用作快捷方式。在這種情況下,我們以稍微不同的方式使用 $emit()
方法。作為事件參數(shù),我們將 update:score
如下所示:this.$emit('update:score', 200)
。然后,當(dāng)我們綁定 score
屬性時(shí),我們添加 .sync
修飾符,如下所示: <child-a :score.sync="score "/>
.在 Parent A 組件中,我們刪除了 updateScore()
?方法和事件注冊(cè) (@updatingScore="updateScore"
),因?yàn)樗鼈儾辉傩枰恕?/p>
Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">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 id="Parent-A">Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
為什么不使用 this.$parent
和 this.$children
進(jìn)行直接父子通信?
Vue 提供了兩種 API 方法,使我們可以直接訪問父組件和子組件:this.$parent
和 this.$children
。一開始,可能很想將它們用作道具和事件的更快、更容易的替代品,但我們不應(yīng)該這樣做。這被認(rèn)為是一種不好的做法或反模式,因?yàn)樗诟附M件和子組件之間形成了緊密耦合。后者會(huì)導(dǎo)致組件不靈活且易于損壞,難以調(diào)試和推理。這些 API 方法很少使用,根據(jù)經(jīng)驗(yàn),我們應(yīng)該避免或謹(jǐn)慎使用它們。
雙向組件通信
道具和事件是單向的。道具下降,事件上升。但是通過一起使用 props 和 events,我們可以在組件樹上有效地進(jìn)行上下通信,從而實(shí)現(xiàn)雙向數(shù)據(jù)綁定。這實(shí)際上是 v-model
指令在內(nèi)部執(zhí)行的操作。
跨組件通信
隨著我們的應(yīng)用程序復(fù)雜性的增加,親子溝通模式很快就會(huì)變得不方便且不切實(shí)際。 props-events 系統(tǒng)的問題在于它直接工作,并且與組件樹緊密綁定。與原生事件相比,Vue 事件不會(huì)冒泡,這就是為什么我們需要重復(fù)發(fā)出它們直到達(dá)到目標(biāo)。結(jié)果,我們的代碼因過多的事件偵聽器和發(fā)射器而變得臃腫。因此,在更復(fù)雜的應(yīng)用程序中,我們應(yīng)該考慮使用跨組件通信模式。
我們看一下下圖:
如您所見,在這種任意類型的通信中,每個(gè)組件都可以發(fā)送和/或接收數(shù)據(jù)來自任何其他組件,無需中間步驟和中間組件。
在以下部分中,我們將探討跨組件通信的最常見實(shí)現(xiàn)。
全局事件總線
全局事件總線是一個(gè) Vue 實(shí)例,我們用它來發(fā)出和監(jiān)聽事件。讓我們?cè)趯?shí)踐中看看它。
const eventBus = new Vue () // 1.Declaring ... Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">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 id="Parent-A">Parent A</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
以下是創(chuàng)建和使用事件總線的步驟:
- 將我們的事件總線聲明為一個(gè)新的 Vue 實(shí)例,如下所示:
const eventBus = new Vue ()
- 從源組件發(fā)出事件,如下所示:
eventBus.$emit('updatingScore', 200)
- 監(jiān)聽目標(biāo)組件中發(fā)出的事件,如下所示:
eventBus.$on('updatingScore', this.updateScore)
在上面的代碼示例中,我們從子級(jí)中刪除 @updatingScore="updateScore"
,并使用 created()
生命周期掛鉤來監(jiān)聽對(duì)于 updatingScore
事件。當(dāng)事件發(fā)出時(shí),將執(zhí)行 updateScore()
方法。我們還可以將更新方法作為匿名函數(shù)傳遞:
created () { eventBus.$on('updatingScore', newValue => {this.score = newValue}) }
全局事件總線模式可以在一定程度上解決事件膨脹問題,但它會(huì)帶來其他問題。可以從應(yīng)用程序的任何部分更改應(yīng)用程序的數(shù)據(jù),而不會(huì)留下痕跡。這使得應(yīng)用程序更難調(diào)試和測(cè)試。
對(duì)于更復(fù)雜的應(yīng)用程序,事情可能很快就會(huì)失控,我們應(yīng)該考慮專用的狀態(tài)管理模式,例如 Vuex,它將為我們提供更細(xì)粒度的控制、更好的代碼結(jié)構(gòu)和組織以及有用的更改跟蹤和調(diào)試功能。
Vuex
Vuex 是一個(gè)狀態(tài)管理庫(kù),專為構(gòu)建復(fù)雜且可擴(kuò)展的 Vue.js 應(yīng)用程序而定制。使用 Vuex 編寫的代碼更加冗長(zhǎng),但從長(zhǎng)遠(yuǎn)來看這是值得的。它對(duì)應(yīng)用程序中的所有組件使用集中存儲(chǔ),使我們的應(yīng)用程序更有組織、透明且易于跟蹤和調(diào)試。商店是完全響應(yīng)式的,因此我們所做的更改會(huì)立即反映出來。
在這里,我將向您簡(jiǎn)要解釋什么是 Vuex,并提供一個(gè)上下文示例。如果您想更深入地了解 Vuex,我建議您看一下我關(guān)于使用 Vuex 構(gòu)建復(fù)雜應(yīng)用程序的專門教程。
現(xiàn)在讓我們研究一下下面的圖表:
如您所見,Vuex 應(yīng)用程序由四個(gè)不同的部分組成:
- 狀態(tài)是我們保存應(yīng)用程序數(shù)據(jù)的位置。
- Getter 是訪問存儲(chǔ)狀態(tài)并將其呈現(xiàn)給組件的方法。
- 突變是實(shí)際且唯一允許改變狀態(tài)的方法。
- 操作是執(zhí)行異步代碼和觸發(fā)突變的方法。
讓我們創(chuàng)建一個(gè)簡(jiǎn)單的商店,看看這一切是如何運(yùn)作的。
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 id="Child-B">Child B</h2> <pre class="brush:php;toolbar:false">data {{ this.$data }}
`, }) Vue.component('ChildA',{ template:` <div id="child-a"> <h2 id="Child-A">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 }}
Grand Parent
data {{ this.$data }}
CodePen 示例
在商店里,我們有以下產(chǎn)品:
- 狀態(tài)對(duì)象中設(shè)置的
score
變量。 incrementScore()
突變,它將按給定值增加分?jǐn)?shù)。- 一個(gè)
score()
getter,它將從狀態(tài)訪問score
變量并將其呈現(xiàn)在組件中。 incrementScoreAsync()
操作,該操作將使用incrementScore()
突變?cè)诮o定時(shí)間段后增加分?jǐn)?shù)。
在 Vue 實(shí)例中,我們不使用 props,而是使用計(jì)算屬性通過 getter 來獲取分?jǐn)?shù)值。然后,為了更改分?jǐn)?shù),在 Child A 組件中,我們使用突變 store.commit('incrementScore', 100)
。在Parent B組件中,我們使用操作 store.dispatch('incrementScoreAsync', 3000)
。
依賴注入
在結(jié)束之前,讓我們探討另一種模式。它的用例主要用于共享組件庫(kù)和插件,但為了完整性值得一提。
依賴注入允許我們通過 provide
屬性定義服務(wù),該屬性應(yīng)該是一個(gè)對(duì)象或返回對(duì)象的函數(shù),并使其可供組件的所有后代使用,而不僅僅是其組件直接孩子。然后,我們可以通過 inject
屬性使用該服務(wù)。
讓我們看看實(shí)際效果:
Vue.component('ChildB',{ template:` <div id="child-b"> <h2 id="Child-B">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 id="Child-A">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 }}
Score: {{ score }}
Grand Parent
data {{ this.$data }}
CodePen 示例
通過使用祖父組件中的 provide
選項(xiàng),我們將 score
變量設(shè)置為可供其所有后代使用。他們每個(gè)人都可以通過聲明 inject: ['score']
屬性來訪問它。而且,正如您所看到的,分?jǐn)?shù)顯示在所有組件中。
注意:依賴注入創(chuàng)建的綁定不是反應(yīng)性的。因此,如果我們希望提供程序組件中所做的更改反映在其后代中,我們必須將一個(gè)對(duì)象分配給數(shù)據(jù)屬性并在提供的服務(wù)中使用該對(duì)象。
為什么不使用 this.$root
進(jìn)行跨組件通信?
我們不應(yīng)該使用 this.$root
的原因與之前描述的 this.$parent
和 this.$children
的原因類似——它創(chuàng)建了太多的依賴關(guān)系。必須避免依賴任何這些方法進(jìn)行組件通信。
如何選擇正確的模式
所以你已經(jīng)了解了組件通信的所有常用方法。但您如何決定哪一個(gè)最適合您的場(chǎng)景呢?
選擇正確的模式取決于您參與的項(xiàng)目或您想要構(gòu)建的應(yīng)用程序。這取決于您的應(yīng)用程序的復(fù)雜性和類型。讓我們探討一下最常見的場(chǎng)景:
- 在簡(jiǎn)單應(yīng)用中,道具和事件將滿足您的所有需求。
- 中端應(yīng)用將需要更靈活的通信方式,例如事件總線和依賴項(xiàng)注入。
- 對(duì)于復(fù)雜的大型應(yīng)用,您肯定需要 Vuex 作為功能齊全的狀態(tài)管理系統(tǒng)的強(qiáng)大功能。
最后一件事。您不必僅僅因?yàn)閯e人告訴您這樣做就需要使用任何已探索的模式。您可以自由選擇和使用您想要的任何模式,只要您設(shè)法保持應(yīng)用程序正常運(yùn)行并且易于維護(hù)和擴(kuò)展。
結(jié)論
在本教程中,我們學(xué)習(xí)了最常見的 Vue.js 組件通信模式。我們了解了如何在實(shí)踐中實(shí)施它們以及如何選擇最適合我們項(xiàng)目的正確方案。這將確保我們構(gòu)建的應(yīng)用程序使用正確類型的組件通信,使其完全正常工作、可維護(hù)、可測(cè)試和可擴(kuò)展。
The above is the detailed content of Design patterns for communication between Vue.js components. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

In the Java framework, the difference between design patterns and architectural patterns is that design patterns define abstract solutions to common problems in software design, focusing on the interaction between classes and objects, such as factory patterns. Architectural patterns define the relationship between system structures and modules, focusing on the organization and interaction of system components, such as layered architecture.

TDD is used to write high-quality PHP code. The steps include: writing test cases, describing the expected functionality and making them fail. Write code so that only the test cases pass without excessive optimization or detailed design. After the test cases pass, optimize and refactor the code to improve readability, maintainability, and scalability.

The Guice framework applies a number of design patterns, including: Singleton pattern: ensuring that a class has only one instance through the @Singleton annotation. Factory method pattern: Create a factory method through the @Provides annotation and obtain the object instance during dependency injection. Strategy mode: Encapsulate the algorithm into different strategy classes and specify the specific strategy through the @Named annotation.

The decorator pattern is a structural design pattern that allows dynamic addition of object functionality without modifying the original class. It is implemented through the collaboration of abstract components, concrete components, abstract decorators and concrete decorators, and can flexibly expand class functions to meet changing needs. In this example, milk and mocha decorators are added to Espresso for a total price of $2.29, demonstrating the power of the decorator pattern in dynamically modifying the behavior of objects.

The SpringMVC framework uses the following design patterns: 1. Singleton mode: manages the Spring container; 2. Facade mode: coordinates controller, view and model interaction; 3. Strategy mode: selects a request handler based on the request; 4. Observer mode: publishes and listen for application events. These design patterns enhance the functionality and flexibility of SpringMVC, allowing developers to create efficient and maintainable applications.

The advantages of using design patterns in Java frameworks include: enhanced code readability, maintainability, and scalability. Disadvantages include complexity, performance overhead, and steep learning curve due to overuse. Practical case: Proxy mode is used to lazy load objects. Use design patterns wisely to take advantage of their advantages and minimize their disadvantages.

PHP design patterns provide known solutions to common problems in software development. Common pattern types include creational (such as factory method pattern), structural (such as decorator pattern) and behavioral (such as observer pattern). Design patterns are particularly useful when solving repetitive problems, improving maintainability, and promoting teamwork. In e-commerce systems, the observer pattern can realize automatic updates between shopping cart and order status. Overall, PHP design patterns are an important tool for creating robust, scalable, and maintainable applications.

TDD and design patterns improve code quality and maintainability. TDD ensures test coverage, improves maintainability, and improves code quality. Design patterns assist TDD through principles such as loose coupling and high cohesion, ensuring that tests cover all aspects of application behavior. It also improves maintainability and code quality through reusability, maintainability and more robust code.
