개발자로서 우리는 관리 및 유지 관리가 가능한 코드를 생성하고 디버그 및 테스트도 더 쉽게 만들고 싶습니다. 이를 달성하기 위해 우리는 패턴이라는 모범 사례를 사용합니다. 패턴은 효율적이고 예측 가능한 방식으로 특정 작업을 수행하는 데 도움이 되는 입증된 알고리즘 및 아키텍처입니다.
이 튜토리얼에서는 가장 일반적인 Vue.js 구성 요소 통신 패턴과 우리가 피해야 할 몇 가지 함정을 살펴보겠습니다. 우리 모두는 실생활에서 모든 문제에 대한 단일 해결책이 없다는 것을 알고 있습니다. 마찬가지로 Vue.js 애플리케이션 개발에는 모든 프로그래밍 시나리오에 적용되는 범용 패턴이 없습니다. 각 모드에는 고유한 장점과 단점이 있으며 특정 사용 사례에 적합합니다.
Vue.js 개발자에게 가장 중요한 것은 주어진 프로젝트에 적합한 패턴을 선택할 수 있도록 가장 일반적인 패턴을 모두 이해하는 것입니다. 이를 통해 정확하고 효율적인 구성 요소 통신이 가능해집니다.
Vue.js와 같은 구성 요소 기반 프레임워크를 사용하여 앱을 빌드할 때 우리의 목표는 앱의 구성 요소를 최대한 격리된 상태로 유지하는 것입니다. 이를 통해 재사용, 유지 관리 및 테스트가 가능해집니다. 구성 요소를 재사용 가능하게 만들려면 해당 구성 요소를 더 추상적이고 분리된(또는 느슨하게 결합된) 형태로 형성해야 합니다. 그러면 애플리케이션의 기능을 중단하지 않고 해당 구성 요소를 애플리케이션에 추가하거나 제거할 수 있습니다.
그러나 애플리케이션 구성 요소 간에 완전한 격리와 독립성을 달성할 수는 없습니다. 어떤 시점에서는 데이터 교환, 애플리케이션 상태 변경 등 서로 통신해야 합니다. 따라서 애플리케이션의 기능, 유연성 및 확장성을 유지하면서 이러한 통신을 올바르게 수행하는 방법을 배우는 것이 중요합니다.
Vue.js에는 구성요소 간 통신의 두 가지 주요 유형이 있습니다.
다음 섹션에서는 적절한 예를 통해 두 가지 유형을 모두 살펴보겠습니다.
Vue.js가 기본적으로 지원하는 구성 요소 통신의 표준 모델은 소품과 사용자 정의 이벤트를 통해 구현되는 상위-하위 모델입니다. 아래 이미지에서 이 모델이 실제로 작동하는 모습을 시각적으로 볼 수 있습니다.
보시다시피 부모는 직계 자식하고만 통신할 수 있고 자식은 부모하고만 직접 통신할 수 있습니다. 이 모델에서는 피어 또는 구성 요소 간 통신이 불가능합니다.
다음 섹션에서는 위 이미지의 구성 요소를 가져와 일련의 실제 예제로 구현해 보겠습니다.
우리가 가지고 있는 구성 요소가 게임의 일부라고 가정해 보겠습니다. 대부분의 게임은 인터페이스 어딘가에 게임 점수를 표시합니다. Parent A 구성 요소에서 score
변수를 선언하고 이를 Child A 구성 요소에 표시한다고 가정해 보겠습니다. 그러면 어떻게 해야 할까요?
부모에서 자식으로 데이터를 전송하기 위해 Vue.js는 props를 사용합니다. 속성을 전달하려면 세 가지 필수 단계가 필요합니다.
props: ["score"]
<span>分数:{{ Score }}</span>
score
变量(在父级模板中),如下所示:<child-a :score="score"></child-a>
변수(상위 템플릿의)에 바인딩합니다. <child-a :score="score"></child-a>
실제로 무슨 일이 일어나고 있는지 더 잘 이해하기 위해 전체 예를 살펴보겠습니다.
으아악CodePen 예제
간결함과 명확성을 위해 속기 변형을 사용하여 소품을 등록합니다. 하지만 실제 개발에서는 props를 검증하는 것이 좋습니다. 이렇게 하면 prop이 올바른 유형의 값을 받게 됩니다. 예를 들어 score
속성은 다음과 같이 확인할 수 있습니다.
props로 작업할 때는 리터럴 변형과 동적 변형의 차이점을 이해해야 합니다. prop을 변수에 바인딩하면 v-bind:score="score"
或其简写 :score="score"
) ,因此,prop 的值将根据变量的值而变化。如果我们只是输入一个没有绑定的值,那么该值将按字面意思解释,并且结果将是静态的。在我们的例子中,如果我们编写 score="score"
100 대신 score가 표시됩니다. 이 미묘한 차이에 주의해야 합니다.
하위 속성 업데이트
Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
我们创建了一个 changeScore()
方法,该方法应该在我们按下更改分数按钮后更新分数。当我们这样做时,似乎分数已正确更新,但我们在控制台中收到以下 Vue 警告:
[Vue warn]:避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,根据 prop 的值使用数据或计算属性。正在变异的道具:“score”
正如你所看到的,Vue 告诉我们,如果父级重新渲染,该 prop 将被覆盖。让我们通过使用内置 $forceUpdate()
方法模拟此类行为来测试这一点:
Vue.component('ParentA',{ template:` <div id="parent-a"> <h2>Parent A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
现在,当我们更改分数,然后按重新渲染父级按钮时,我们可以看到分数从父级返回到其初始值。所以 Vue 说的是实话!
但请记住,数组和对象将影响它们的父对象,因为它们不是被复制,而是通过引用传递。
因此,当我们需要改变子级中的 prop 时,有两种方法可以解决这种重新渲染的副作用。
第一种方法是将 score
属性转换为本地数据属性 (localScore
),我们可以在 changeScore( )
方法和模板中:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
现在,如果我们在更改分数后再次按渲染父项按钮,我们会看到这次分数保持不变。
第二种方法是在计算属性中使用 score
属性,它将被转换为新值:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
在这里,我们创建了一个计算的 doubleScore()
,它将父级的 score
乘以 2,然后将结果显示在模板中。显然,按渲染父级按钮不会产生任何副作用。
现在,让我们看看组件如何以相反的方式进行通信。
我们刚刚了解了如何改变子组件中的某个 prop,但是如果我们需要在多个子组件中使用该 prop 该怎么办?在这种情况下,我们需要从父级中的源中改变 prop,这样所有使用该 prop 的组件都将被正确更新。为了满足这一要求,Vue 引入了自定义事件。
这里的原则是,我们通知父级我们想要做的更改,父级执行该更改,并且该更改通过传递的 prop 反映。以下是此操作的必要步骤:
this.$emit('updatingScore', 200)
@updatingScore="updateScore"
this.score = newValue
让我们探索一个完整的示例,以更好地理解这是如何发生的:
Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
我们使用内置的 $emit()
方法来发出事件。该方法有两个参数。第一个参数是我们要发出的事件,第二个参数是新值。
.sync
修饰符Vue 提供了 .sync
修饰符,其工作原理类似,在某些情况下我们可能希望将其用作快捷方式。在这种情况下,我们以稍微不同的方式使用 $emit()
方法。作为事件参数,我们将 update:score
如下所示:this.$emit('update:score', 200)
。然后,当我们绑定 score
属性时,我们添加 .sync
修饰符,如下所示: <child-a :score.sync="score "/>
.在 Parent A 组件中,我们删除了 updateScore()
方法和事件注册 (@updatingScore="updateScore"
),因为它们不再需要了。
Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
this.$parent
和 this.$children
进行直接父子通信?Vue 提供了两种 API 方法,使我们可以直接访问父组件和子组件:this.$parent
和 this.$children
。一开始,可能很想将它们用作道具和事件的更快、更容易的替代品,但我们不应该这样做。这被认为是一种不好的做法或反模式,因为它在父组件和子组件之间形成了紧密耦合。后者会导致组件不灵活且易于损坏,难以调试和推理。这些 API 方法很少使用,根据经验,我们应该避免或谨慎使用它们。
道具和事件是单向的。道具下降,事件上升。但是通过一起使用 props 和 events,我们可以在组件树上有效地进行上下通信,从而实现双向数据绑定。这实际上是 v-model
指令在内部执行的操作。
随着我们的应用程序复杂性的增加,亲子沟通模式很快就会变得不方便且不切实际。 props-events 系统的问题在于它直接工作,并且与组件树紧密绑定。与原生事件相比,Vue 事件不会冒泡,这就是为什么我们需要重复发出它们直到达到目标。结果,我们的代码因过多的事件侦听器和发射器而变得臃肿。因此,在更复杂的应用程序中,我们应该考虑使用跨组件通信模式。
我们看一下下图:
如您所见,在这种任意类型的通信中,每个组件都可以发送和/或接收数据来自任何其他组件,无需中间步骤和中间组件。
在以下部分中,我们将探讨跨组件通信的最常见实现。
全局事件总线是一个 Vue 实例,我们用它来发出和监听事件。让我们在实践中看看它。
const eventBus = new Vue () // 1.Declaring ... Vue.component('ChildA',{ template:` <div id="child-a"> <h2>Child A</h2> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
CodePen 示例
以下是创建和使用事件总线的步骤:
const eventBus = new Vue ()
eventBus.$emit('updatingScore', 200)
eventBus.$on('updatingScore', this.updateScore)
在上面的代码示例中,我们从子级中删除 @updatingScore="updateScore"
,并使用 created()
生命周期挂钩来监听对于 updatingScore
事件。当事件发出时,将执行 updateScore()
方法。我们还可以将更新方法作为匿名函数传递:
created () { eventBus.$on('updatingScore', newValue => {this.score = newValue}) }
全局事件总线模式可以在一定程度上解决事件膨胀问题,但它会带来其他问题。可以从应用程序的任何部分更改应用程序的数据,而不会留下痕迹。这使得应用程序更难调试和测试。
对于更复杂的应用程序,事情可能很快就会失控,我们应该考虑专用的状态管理模式,例如 Vuex,它将为我们提供更细粒度的控制、更好的代码结构和组织以及有用的更改跟踪和调试功能。
Vuex 是一个状态管理库,专为构建复杂且可扩展的 Vue.js 应用程序而定制。使用 Vuex 编写的代码更加冗长,但从长远来看这是值得的。它对应用程序中的所有组件使用集中存储,使我们的应用程序更有组织、透明且易于跟踪和调试。商店是完全响应式的,因此我们所做的更改会立即反映出来。
在这里,我将向您简要解释什么是 Vuex,并提供一个上下文示例。如果您想更深入地了解 Vuex,我建议您看一下我关于使用 Vuex 构建复杂应用程序的专门教程。
现在让我们研究一下下面的图表:
如您所见,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> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
data {{ this.$data }}
data {{ this.$data }}
CodePen 示例
在商店里,我们有以下产品:
score
变量。incrementScore()
突变,它将按给定值增加分数。score()
getter,它将从状态访问 score
变量并将其呈现在组件中。incrementScoreAsync()
操作,该操作将使用 incrementScore()
突变在给定时间段后增加分数。 在 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> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">data {{ this.$data }}
data {{ this.$data }}
data {{ this.$data }}
CodePen 示例
通过使用祖父组件中的 provide
选项,我们将 score
变量设置为可供其所有后代使用。他们每个人都可以通过声明 inject: ['score']
属性来访问它。而且,正如您所看到的,分数显示在所有组件中。
注意:依赖注入创建的绑定不是反应性的。因此,如果我们希望提供程序组件中所做的更改反映在其后代中,我们必须将一个对象分配给数据属性并在提供的服务中使用该对象。
this.$root
进行跨组件通信?我们不应该使用 this.$root
的原因与之前描述的 this.$parent
和 this.$children
的原因类似——它创建了太多的依赖关系。必须避免依赖任何这些方法进行组件通信。
所以你已经了解了组件通信的所有常用方法。但您如何决定哪一个最适合您的场景呢?
选择正确的模式取决于您参与的项目或您想要构建的应用程序。这取决于您的应用程序的复杂性和类型。让我们探讨一下最常见的场景:
最后一件事。您不必仅仅因为别人告诉您这样做就需要使用任何已探索的模式。您可以自由选择和使用您想要的任何模式,只要您设法保持应用程序正常运行并且易于维护和扩展。
在本教程中,我们学习了最常见的 Vue.js 组件通信模式。我们了解了如何在实践中实施它们以及如何选择最适合我们项目的正确方案。这将确保我们构建的应用程序使用正确类型的组件通信,使其完全正常工作、可维护、可测试和可扩展。
위 내용은 Vue.js 구성 요소 간 통신을 위한 디자인 패턴의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!