概述
在Vue.js 2.x中,model.sync
将被弃用。
那么,在Vue.js 2.x中的同级组件之间进行通信的正确方式是什么呢?
背景
根据我对Vue.js 2.x的理解,兄弟通信的首选方法是使用存储或事件总线。
根据Evan(Vue.js的创建者):
还值得一提的是,“在组件之间传递数据”通常是一个坏主意,因为最终数据流变得不可跟踪,很难调试。
如果一段数据需要由多个组件共享,则首选global stores或Vuex。
[Link to discussion]我的天啊
还有:
不推荐使用.once
与.sync
.属性现在始终单向关闭.若要在父作用域中产生副作用,组件需要显式emit
事件,而不是依赖隐式绑定.
因此,Evan建议使用$emit()
和$on()
。
问题
我担心的是:
- 每个
store
和event
都具有全局可见性(如果我错了,请纠正我); - 为每一个次要的通信创建一个新的存储区太浪费了;
我想要的是一些scopeevents
或stores
对兄弟组件的可见性。(或者也许我没有理解上面的想法。)
问题
那么,在同级组件之间进行通信的正确方式是什么呢?
7条答案
按热度按时间e5nqia271#
您甚至可以将其缩短,并使用根
Vue
示例作为全局事件集线器:第一部分:
第二部分:
y3bcpkx12#
在Vue.js 2.0中,我使用了文档中演示的eventHub机制。
1.定义集中式事件中心。
1.现在,在组件中,您可以使用
1.你要听
更新
请看alex的回答,它描述了一个更简单的解决方案。
vbopmzt13#
***免责声明:*此答案是很久以前写的,可能无法反映最新的Vue开发或趋势。请对此答案中的所有内容持保留态度,如果您发现任何过时、不再有效或无用的内容,请进行评论。
状态范围
在设计Vue应用程序(或者实际上,任何基于组件的应用程序)时,存在不同类型的数据,这些数据取决于我们处理的关注点,并且每种数据都有自己的首选通信通道。
***全局状态:**可能包括登录用户、当前主题等。
***本地状态:**表单属性、禁用按钮状态等。
请注意,部分全局状态可能在某个点上以本地状态结束,并且它可以像任何其他本地状态一样传递给子组件,无论是完全传递还是稀释传递以匹配用例。
通信信道
通道是一个松散的术语,我将使用它来指代围绕Vue应用程序交换数据的具体实现。
每种实现都针对特定的通信通道,包括:
不同的关注涉及不同的沟通渠道。
Props:我的天啊!直接父子
Vue中用于单向数据绑定的最简单通信通道。
Events:直接子-父
***重要通知:*Vue版本3中的
$on
and$once
were removed。$emit
和v-on
事件侦听器。用于直接子级与父级通信的最简单通信通道。事件启用双向数据绑定。提供/注入:全局或远程本地状态
在Vue 2.2+中添加的,与React的上下文API非常相似,这可以用作事件总线的可行替代品。
在组件树中的任何一点上,组件都可以 * 提供 * 某些数据,这一行中的任何子组件都可以通过
inject
组件的属性访问这些数据。这可用于在应用程序的根提供全局状态,或在树的子集内提供本地化状态。
集中存储(全局状态)
Vuex是一个状态管理模式+库,用于Vue.js应用程序。它作为应用程序中所有组件的集中存储,并具有确保状态只能以可预测的方式变化的规则。
而现在你问:
我是否应该为每个次要通信创建vuex存储?
在处理全局状态时,它确实大放异彩,这包括但不限于:
因此,您的组件可以真正专注于它们应该做的事情,管理用户界面,而全局存储可以管理/使用常规业务逻辑,并通过getters和actions提供清晰的API。
这并不意味着您不能将其用于组件逻辑,但我个人认为该逻辑的范围是一个命名空间的Vuex module,其中只有必要的全局UI状态。
引用和方法:边缘案例
尽管存在道具和事件,有时您可能仍然需要直接访问JavaScript中的子组件。
它仅用作直接子操作的转义影线-应避免从模板或计算属性中访问
$refs
。如果您发现自己经常使用refs和child方法,那么可能是时候进行lift the state up或考虑这里或其他答案中描述的其他方法了。
$parent
:边缘案例与
$root
类似,$parent
属性可用于从子级访问父级示例。这可能是一个吸引人的方法,作为用prop传递数据的一种惰性替代方法。在大多数情况下,进入父组件会使应用程序更难调试和理解,特别是在父组件中的数据发生了变化的情况下。当以后查看该组件时,很难找出变化的来源。
实际上,您可以使用
$parent
、$ref
或$root
来导航整个树结构,但这类似于让所有内容都是全局的,并且很可能变成不可维护的意大利面条。事件总线:全局/远程本地状态
这是过去的模式,从上到下到处传递道具到嵌套很深的子组件,几乎没有其他组件需要这些。对于精心选择的数据,请谨慎使用。
**请注意:**随后创建的将自身绑定到事件总线的组件将被多次绑定--导致触发多个处理程序和泄漏。我个人从未感觉到在我过去设计的所有单页面应用程序中需要事件总线。
下面演示了一个简单的错误如何导致泄漏,即使从DOM中删除了
Item
组件,该组件仍然会触发。第一次
请记住删除
destroyed
生命周期挂接中的侦听器。组件类型
***免责声明:*下面的"containers" versus "presentational" components只是构建项目的一种方法,现在有多种替代方法,比如新的Composition API,它可以有效地取代我在下面描述的“应用程序特定容器”。
为了协调所有这些通信,为了简化可重用性和测试,我们可以将组件视为两种不同的类型。
同样,这并不意味着通用组件应该被重用,或者应用程序特定的容器不能被重用,而是它们有不同的职责。
应用程序特定容器
***注意:*请参阅新的组合API作为这些容器的替代。
这些只是 Package 其他Vue组件(通用或其他应用程序特定容器)的简单Vue组件。这是Vuex商店通信应该发生的地方,该容器应该通过其他更简单的方式(如道具和事件侦听器)进行通信。
这些容器甚至可以完全不包含本地DOM元素,而让通用组件处理模板和用户交互。
作用域以某种方式对同级组件显示
events
或stores
大多数组件并不知道存储区的存在,这个组件应该(主要)使用一个命名空间存储区模块,并通过提供的Vuex绑定助手应用一组有限的
getters
和actions
。通用/表示组件
它们应该从props接收数据,对本地数据进行修改,并发出简单的事件。大多数时候,它们应该根本不知道Vuex商店的存在。
它们也可以被称为容器,因为它们的唯一职责可能是分派到其他UI组件。
同级通信
那么,在完成所有这些之后,我们应该如何在两个兄弟组件之间进行通信呢?
下面的例子更容易理解:假设我们有一个输入框,它的数据应该在整个应用程序中共享(在树中不同位置的兄弟),并通过后端持久化。
混合问题
从最坏情况场景开始,我们的组件将混合 * 表示 * 和 * 业务 * 逻辑。
虽然对于一个简单的应用程序来说,它看起来不错,但它也有很多缺点:
关注点分离
为了区分这两个问题,我们应该将组件 Package 在应用程序特定的容器中,并将表示逻辑保存在通用输入组件中。
使用以下模式,我们可以:
我们的输入组件现在是可重用的,并且不知道后端和同级。
我们的应用程序特定容器现在可以成为业务逻辑和表示通信之间的桥梁。
由于Vuex store actions 处理后端通信,因此这里的容器不需要了解axios和后端。
ffx8fchx4#
好的,我们可以通过
v-on
事件通过父级在兄弟之间进行通信。让我们假设,当我们点击
List
中的某个元素时,我们想要更新Details
组件。在
Parent
中:模板:
在这里:
v-on:select-item
这是一个事件,将在List
组件中调用(见下文);setSelectedItem
是Parent
的方法来更新selectedModel
;JavaScript语言:
在
List
中:模板:
JavaScript语言:
在这里:
this.$emit('select-item', item)
将通过select-item
直接在父视图中发送一个项目。父视图将把它发送到Details
视图。qvk1mo1f5#
如何处理兄弟之间的通信取决于具体情况。但首先我要强调的是,全局事件总线方法在Vue.js 3中将消失。请参见RFC。因此,有了这个答案。
最低公共祖先模式(或“LCA”)
大多数情况下,我建议使用lowest common ancestor模式(也称为“数据向下,事件向上”)。这种模式易于阅读、实现、测试和调试。它还创建了一个优雅、简单的数据流。
本质上,这意味着如果两个组件需要通信,则将它们的共享状态放在最近的组件中,这两个组件共享为祖先。通过属性将数据从父组件传递到子组件,并通过发出事件将信息从子组件传递到父组件(下面的示例代码)。
例如,您可能有一个电子邮件应用程序:地址组件需要与消息正文组件进行数据通信(可能是为了预先填充“Hello“),因此它们使用最接近的共享祖先(可能是电子邮件表单组件)来保存收件人数据。
如果事件和道具需要经过许多“中间人”组件,LCA可能会很烦人。
要了解更多细节,我建议同事们参考this excellent blog post。(忽略它的示例使用Ember的事实,它的概念适用于许多框架)。
数据容器模式(例如Vuex)
对于父-子通信涉及太多中间人的复杂情况,请使用Vuex或等效的数据容器技术。
当单个存储变得过于复杂或混乱时,请使用命名空间模块。例如,为具有许多相互连接的复杂组件集合(如复杂日历)创建单独的命名空间可能是合理的。
发布/订阅(事件总线)模式
如果事件总线(即publish/subscribe)模式对您的应用更有意义(从架构的Angular ),或者您需要从现有的Vue.js应用中删除Vue.js的全局事件总线,Vue.js核心团队现在建议使用第三方库,如mitt。(请参阅第1段中引用的RFC)。
其他
这里有一个小的(可能过于简单)LCA解决方案的例子,这个例子是一个名为whack-a-mole的游戏。
在这个游戏中,玩家“击杀”一只鼹鼠,使它隐藏起来,然后另一只鼹鼠在随机地点出现,就会得到积分。要构建这个包含“鼹鼠”组件的应用程序,人们可能会想,“鼹鼠组件N应该在被击杀后告诉鼹鼠组件Y出现”。但Vue.js不鼓励这种组件通信方法,因为Vue.js应用程序(和html)实际上是tree data structures的。
这可能是一件好事。一个大型/复杂的应用程序,其中节点之间相互通信,没有任何集中的管理器,可能很难调试。此外,使用LCA的组件往往表现出低coupling和高reusability。
在此示例中,游戏管理器组件将mole可见性作为道具传递给mole子组件。当“击”(单击)可见的mole时,它将发出一个事件。游戏管理器组件(公共ancestor)接收该事件并修改其状态。Vue.js自动更新道具,因此所有mole组件都将接收新的可见性数据。
第一个
rxztt3cl6#
如果我想“破解”Vue.js中的正常通信模式,特别是现在
.sync
被弃用了,我通常做的是创建一个简单的EventEmitter来处理组件之间的通信。使用此
Transmitter
对象,您可以在任何组件中执行以下操作:并创建一个“接收”组件:
同样,这是针对特定用途的,不要将整个应用程序都建立在这种模式上,而是使用类似
Vuex
的模式。l3zydbqr7#
在我的例子中,我有一个带有可编辑单元格的表格。当用户从一个单元格单击到另一个单元格编辑内容时,我只希望一次有一个单元格是可编辑的。解决方案是使用父子(道具)与亲子(事件)。在下面的示例中,我将在“rows”的数据集上循环,并使用rowIndex和cellIndex创建一个唯一的每个单元格的(坐标)标识符。当单击单元格时,从子元素向上到父元素都会触发一个事件,告诉父元素单击了哪个坐标。然后父元素设置selectedCoord并将其向下传递给子组件。因此每个子组件都知道自己的坐标和所选的坐标,然后它可以决定是否使自己可编辑。