[VueJS] 元件溝通

大家好,打給厚,胎嘎後,身為一個專業程式踩雷王,種田的時候挖到地雷也是無可厚非(欸,今天要給大家講一下關於 VueJS 元件(Component 與 Directive 通用)之間的溝通,的一些小技(ㄉㄧˋ)巧(ㄌㄟˊ)


首先要看一下官方給的說明,

http://vuejs.org/guide/reactivity.html

VueJS-How Changes Are Tracked

接著看一下一個 Vue 元件的結構,

http://vuejs.org/guide/instance.html#Lifecycle-Diagram

VueJS-Lifecycle Diagram

收工(喂!

元件溝通

不外乎就幾種方式,

  1. 利用 Event 來傳遞訊息
  • $dispatch() 對上屬元件做廣播(會一直廣播到 $root
  • $broadcast() 對下屬元件做監聽廣播
  • $emit() 對屬於 同一個元件 內的監聽做觸發動作
  • $on() 設定一個監聽
  1. 利用 $watch 來監聽某個物件或資料
  2. 利用雙向綁定(例如 :data.sync="data")搭配 2 來變更資料

最偷懶的方式其實是第 3 種(炸

溝通之後的 DOM 資料更新

Vue 提供了一個 $nextTick 的方法來更新你的 DOM(類似於 ng 裡面的 scope.$digest() 這樣的方法,問題來了,要放在哪裡?

  1. 放在 Event 接收的地方?
  2. 放在 methodsdata 變更來呼叫?
  3. 放在 $watch

答案是全部都可以,但是全部都有雷。

使用 Event 溝通

單純使用 $on 來接收,並搭配 $nextTick 基本上不會有問題。但是,如果又加上 $watch 就會有問題,

this.$on('data-changed', (newData) => {
  this.data = newData

  // 或

  this.$nextTick(() => {
    this.data = newData
  })
})

...

watch: {
  data (newData) {
    console.log(newData)
    // 或加上
    this.$nextTick(() => {
      console.log(newData)
      // 做一些 DOM 改變
    })
  }
}

這樣的作法,如果觸發 data-changed,那麼 watch 需要搭配 deep 才會被觸發(如果你是 Object 或是 Array,你可以使用 setTimeout 測試看看。另外,如果是改了資料來源,例如他是一個 JSON 檔案,那麼修改來源檔案(或許由 AJAX 讀入,觸發的結果跟上述相同,watch 也是需要搭配 deep

另外,Event 搭配 methods 呼叫是可以的,不管你的 $nextTick 要放在 methods 裡面,還是 Event 裡面都可以。只要你是要改變 DOM 所產出的東西,都記得要放在 $nextTick 裡面。

Methods 方法

你可以在 Components 裡面的 methods 加入一個函式,用他來處理一些事情之後,最後在將資料返回,或是直接做 $nextTick 處理掉。

$watch 到底在看誰?

Watch 只會看元件的 props, data,所以其他的東西 watch 是不會理你的。同樣的,watch 裡面也必須要有 $nextTick 來更新你的 DOM 所顯示的資料。

小結

元件的溝通,用雙向綁定會有一個問題,當你的元件越來越多,或是深度越來越深的時候,你的綁定資料就會變得複雜,舉例來說,

元件順序 A > B > C

這是元件 A,提供最原始的 data

<template>
  <div>
    <b-component :data.sync="data"></b-component>
  </div>
</template>

<script>
import bComponent from './bComponent'

export default {
  name: 'aComponent',
  components: {
    bComponent
  },
  data: {
    data: { name: 'a' }
  }
}
</script>

這是元件 B

<template>
  <div>
    <c-component :data.sync="data"></c-component>
  </div>
</template>

<script>
import cComponent from './cComponent'

export default {
  name: 'bComponent',
  components: {
    cComponent
  }
}
</script>

這是元件 C,我要用 data

<template>
  <div>
    <c-component></c-component>
  </div>
</template>

<script>
export default {
  name: 'cComponent',
  attached () {
    // 這裡可以拿到從 A > B 過來的 data 資料
    console.log(this.data)
  }
}
</script>

如果你只用雙向綁定,那你必須要每一層 Component 都把 data 傳進去。這樣會造成元件後續開發上的難度。所以,在這種情況下,改用 Event 會比較容易理解,維護上也比較輕鬆一點。

這是元件 A,提供最原始的 data

<template>
  <div>
    <b-component></b-component>
  </div>
</template>

<script>
import bComponent from './bComponent'

export default {
  name: 'aComponent',
  components: {
    bComponent
  },
  data: {
    data: { name: 'a' }
  },
  compiled () {
    this.$broadcast('send-data-to-c-component', this.data)
  }
}
</script>

這是元件 C,我要用 data

<template>
  <div>
    <c-component></c-component>
  </div>
</template>

<script>
export default {
  name: 'cComponent',
  created () {
    this.$on('send-data-to-c-component', (data) => {
      // 這裡可以拿到從 A 過來的 data 資料
      console.log(data)
      // 可以把 data 指給自己
      this.data = data
    })
  },
  data: {
    data: undefined
  }
}
</script>

中間的 bComponent 不需要做什麼其他的事情。其實作法跟 ng 很類似。

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei