淺談Vue元件傳遞資料與通訊

Zegendary發表於2017-12-13

對於使用Vue的新手來說,元件之間的資料傳遞一直是一個比較頭疼的問題,在實際開發中我也踩了些坑,簡單的做一個總結:

父子元件之間的資料傳遞

  1. 父元件向子元件傳遞資料 由於元件例項的作用域是**孤立(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>
複製程式碼
  1. 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()
  }
}
複製程式碼
  1. 子元件向父元件傳遞事件 上面提到了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 看到 完~

相關文章