在 Vue 中通常以一个 Vue 实例来表示一个应用,一个应用由若干个组件拼装而成。
没错,就像“装机”一样。当你把主板、CPU、显卡、内存、散热器、SSD、电源等摆放到机箱的各个位置后,很明显产生了一个明确的需求:我怎么让这些东西协同工作?
回到 Vue 中,处理不同组件之间的数据或状态是一件经常遇到的事。
好在 Vue的文档 足够详细。关于组件交互的部分,如果没有实际需求,我表示难以明白props
以及自定义事件等使用场景是什么。
Props
props
是定义在子组件中的属性,用来定义期望从父组件传下来的数据。
从实际场景着手,写一个简单的需求。
<template> <p>{{helloWorld}}</p> </template> <template> <input type="text" v-model="text"> <child></child> </template>
|
当父组件中输入内容时显示到子组件中。
这时,我需要在子组件中声明一个 props 属性来接收父组件中输入的内容。
module.exports = { props: { helloWorld: String } }; module.exports = { components: { child: require('child') }, data: function(){ return { text: '' } } };
|
还需要告诉子组件,它的 props 对应父组件中的哪个数据。
<child :hello-world="text"></child>
|
camelCase 格式属性用作 HTML 特性时需要转换成 kebab-case 格式。
当前需求轻松的解决了!
有没有发现 props 好像是单向的,父 -> 子?
不,并不是。只是默认是单向的。可以通过添加额外的修饰符来显示强制双向或单词绑定。
<child :hello-world.sync="text"></child> <child :hello-world.once="text"></child>
|
而正像 Vue 文档中所说的,默认单向绑定是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
举个例子,如果当前子组件设置为双向绑定,另有其他的子组件依赖父组件的helloWorld
属性。这种关系下如果子组件修改了数据,势必引起其他子组件的状态改变。某些场景下我们并不希望发生这种情况。
自定义事件
使用自定义事件也可以实现父子组件之间的通信,通过事件触发的形式来传递数据。
- 使用
$on()
监听事件; - 使用
$emit()
在它上面触发事件; - 使用
$dispatch()
派发事件,事件沿着父链冒泡; - 使用
$broadcast()
广播事件,事件向下传导给所有的后代。
现在变更需求,把输入框拿出来作为子组件B。
<template> <input type="text" > </template> <template> <child-b></child-b> <child></child> </template>
|
当子组件B内容变化时,我应当通知父组件:头儿,我的工作完成了。
<template> <input type="text" v-model="text" @change="onInput"> </template> <script> module.exports = { data: function () { return { text: '' } }, methods: { onInput: function () { if(this.text.trim()) { this.$dispatch('child-next', this.text); } } } }; </script>
|
父组件收到通知后,广播给其他需要的子组件:B已经完成XXX了,剩下的东西交给你了。
module.exports = { components: { child: require('child'), 'child-b': require('childB') }, events: { 'child-next': function (text) { this.$broadcast('child-finish', text); } } };
|
或者为了能从父组件中直观的看出事件来源,可以使用显示声明绑定事件。
<template> <child-b @child-next="handle"></child-b> <child></child> </template> <script> module.exports = { components: { child: require('child'), 'child-b': require('childB') }, methods: { handle: function (text) { this.$broadcast('child-finish', text); } } }; </script>
|
接收广播的子组件,需要添加对应的处理事件。
<template> <p>{{helloWorld}}</p> </template> <script> module.exports = { data: function () { return { helloWorld: '' } }, events: { 'child-finish': function (text) { this.helloWorld = text; } } }; </script>
|
与 props 方式相比,自定义事件的方式各个组件的数据独立,不会被父或子组件轻易修改。因为我们能控制在何时进行事件派发和广播。
当然这两种方式并不冲突,可以结合使用来创造最佳实践。
Vuex
最后就是使用大杀器 Vuex 了。
不管是props
还是自定义事件
,如果数据要由子组件到另一个子组件中,都要进行父组件的中转。随着项目的逐步增大,数据流也会变得复杂,难以管理和发现问题。
而 Vuex 就是独立的一个数据管理层。你需要把组件的本地状态和应用状态区分开来,把应用状态交由 Vuex 来管理,方便每一个组件去交换数据更新状态。
这是一个简单的例子