對於使用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 看到 完~