浅谈Vue组件传递数据与通信
对于使用Vue的新手来说,组件之间的数据传递一直是一个比较头疼的问题,在实际开发中我也踩了些坑,简单的做一个总结:
父子组件之间的数据传递
- 父组件向子组件传递数据
由于组件实例的作用域是孤立(scope)的,所以组件之间无法相互访问到对方的数据,所以这里我们需要在子组件中使用props
选项去接受来自父组件传递进来的动态数据,并且在父组件的标签上绑定v-bind
该数据,这样一来,我们就把父组件中的数据传递给了子组件中。
Vue.component('child', {
// declare the props
props: ['message'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<span>{{ message }}</span>'
})
<child :message="data"></child>
- props数据单向传递
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
所以,当我们的控制台出现这样的报错时,那就说明你的被你改变了:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"
警告告诉我们需要使用data
或者computed
选项将prop所接受的数据处理一下即可,所以我们有如下两种方式:
- 定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
- 定义一个计算属性,处理 prop 的值并返回。
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
- 子组件向父组件传递事件
上面提到了prop
是单向数据流的,所以prop
接受的数据是无法双向绑定的,那么我们如何才能改变父组件的数据呢?这里我们就用到了vue的自定义事件。
子组件中我们可以通过$emit(eventName)
来触发事件。
父组件中我们可以通过$on(eventName)
来监听事件,如果对事件发布订阅模式比较熟悉的同学应该会比较容易理解。
-
v-on
监听事件
<!-- html -->
<div id="app1">
<input type="text" v-model="message">
//v-on 监听子组件 传递的input事件
<child1 :value="message" v-on:input="setVal"></child1>
</div>
//javascript
Vue.component('child1', {
props: ['value'],
//定义一个计算属性,处理 prop 的值并返回
computed: {
newVal: {
get() { //用于获取计算属性
return this.value
},
set(v) { //用于设置计算属性
return v
}
}
},
template: '<input type="text" :value="newVal" @input="onInput">',
methods: {
onInput: function(e) {
this.newVal = e.target.value
this.$emit('input', e.target.value)
}
}
})
var app1 = new Vue({
el: '#app1',
data: function() {
return {
message: ''
}
},
methods: {
setVal: function(v){
this.message = v
}
}
})
这样一来,我们便实现了父子组件数据的双向绑定。不过看起来比较麻烦,下面再介绍几种语法糖,让父子组件通信更简洁
- v-model
我们都知道,v-model是一个语法糖,当我们在input
里面写了v-model="xxx"
时相当于写了<input :value="xxx" @input="xxx = $event.target.value">
,所以我们可以利用这个特性简化一下:
<div id="app">
<input type="text" v-model="message">
<child v-model="message"></child>
</div>
Vue.component('child', {
props: ['value'],
template: '<input type="text" :value="value" @input="onInput">',
methods: {
onInput: function(e) {
this.$emit('input', e.target.value)
}
}
})
var app = new Vue({
el: '#app',
data: function() {
return {
message: ''
}
}
})
- .sync修饰符
文档中这样说道:
在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』。事实上,这正是 Vue 1.x 中的 .sync修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了『单向数据流』的假设。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。
.sync 修饰符目的就是为了让父子组件的数据双向绑定更加简单。例
<div id="app2">
<input type="text" v-model="message">
<child2 :message.sync="message"></child2>
</div>
Vue.component('child2', {
props: ['message'],
template: '<input type="text" :value="message" @input="onInput">',
methods: {
onInput: function(e) {
this.$emit('update:message', e.target.value)
}
}
})
var app2 = new Vue({
el: '#app2',
data: {
message: ''
}
})
注意 子组件emit
事件的时候,需要加上update
- 利用引用类型进行双向绑定
因为JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。代码更加简单了。
<div id="app3">
<input type="text" v-model="message.val">
<child3 :message="message"></child3>
</div>
Vue.component('child3', {
props: ['message'],
template: '<input type="text" v-model="message.val">'
})
var app3 = new Vue({
el: '#app3',
data: {
message: {
val: ''
}
}
})
这样写是更简单,但同时你的数据更不可控。
非父子组件之间的数据传递
对于非父子组件通信,在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})
这个空的实例就好像一辆汽车,将两个组件紧紧的联系起来,所以又称 eventBus
。下面在具体的例子中去使用它:
<div id="app4">
<child4></child4>
<child5></child5>
</div>
//创建空的实例
var Bus = new Vue()
Vue.component('child4', {
data: function() {
return {
message: ''
}
},
template: '<input type="text" :value="message" @input="onInput">',
methods: {
onInput(e) {
Bus.$emit('send', e.target.value)
}
}
})
Vue.component('child5', {
data() {
return {
message: ''
}
},
created: function() {
Bus.$on('send', function(value) {
this.message = value
}.bind(this)) //若不bind this,回调中的this指向Bus
},
template: '<span>{{message}}</span>'
})
var app4 = new Vue({
el: '#app4'
})
当然,eventBus
也可以在父子组件的通信中使用,不过略为繁琐,这里不做介绍。如果非父子组件通信比较复杂时,我们可以通过Vuex
来完美解决。
PS:所有的示例可以在 http://js.jirengu.com/dosef/1/edit?html,js,output 看到
完~