iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Modern Web

Vue.js 進階心法系列 第 7

用 watch 搭配服用 immutable

  • 分享至 

  • xImage
  •  

在 《Clean Architecture》裡第 6 章介紹 functional programming ,有提到一個很重要的觀念 - 不可變動性 (immutable)。

這是什麼意思呢?在 JavaScript 中,對於複雜型別 (常見的型別是 Object 或 Array) 的修改,會選擇替換掉物件本身,就能做到 immutable。

那這件事和 watch 有什麼關係呢?

在 Vue.js 的畫新畫面邏輯中「資料改變,畫面就改變」,要怎麼樣才是「正確的資料改變」呢?

Vue.js 的 watch

官網文件中的 API 介紹中,有完整的 watch 說明。

在此將它的介紹拆成兩個部份,會有助於觀念上的釐清。
對於四種簡單型別的 watch 方式。

第一種就是直接用一個和 data 同名的 watch 。讓資料在 set 的同時,可以呼叫一次這個放在 watch 的 function

export default {
  data: {
    a: 1
  },
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    }
  }
}

第二種,就是將 method 放到 watch 上面,因為有時候有些 method 會主動觸發,有時又要被動觸發,就必須這樣安排,不過另一種做法也可以放在第一種方法的 function 中再呼叫 someMethod

export default {
  data: {
    b: 2
  },
  watch: {
    // string method name
    b: 'someMethod',
  },
  methods: {
    someMethod() {
      // do something...
    }
  }
}

第三種做法,watch 裡放的就不只是 method 了。而是一個 config ,註冊了一個 handler 並且設定它要 deep:true 的這樣觸發。

第四種做法就出現 immediate 這個關鍵字,並且也是設定成 true。

export default {
  data: {
    c: 3,
    d: 4
  },
  watch: {
    // the callback will be called whenever any of the watched object properties change regardless of their nested depth
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // the callback will be called immediately after the start of the observation
    d: {
      handler: 'someMethod',
      immediate: true
    }
  }
}

最後一做法,在範例程式碼中,有出現較深的資料結構。
如果 watch 較深的資料時,可以直接用 e.f 來監聰深度的結構改變。

export default {
  data: {
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    // you can pass array of callbacks, they will be called one-by-one
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }
}

用替換了哪個記憶體位址了解 immutable[1]

let user = {
  name: 'Chris',
  age: 18,
};

畫成記憶體圖,如下圖 (假設 JavaScript 是以 call by sharing 實作的話)

修改的話,可以畫成下圖,接下來就來解釋兩種修改方式。

如果是 mutable (可變的,就直接改掉 user 的成員變數所指向的位置),將 user.age 改成 19 的動作,就是指向 0xFF0400 指向 0xFF0408

程式碼如下

let user = {
  name: 'Chris',
  age: 18,
};

user.age = 19

對於修改記憶體位址的位置來說,就不是 user 被替換,而是 user.age 的位置被替換。

如果要 immutable 就是換掉 user 為目標。(修改的是藍色線的指向,要指向新的一個物件),將 user 改成 另一個 object 的動作,就是指向 0xFF0200 指向 0xFF0210

程式碼如下

let user = {
  name: 'Chris',
  age: 18,
};

user = {
  name: 'Chris',
  age: 19,
}

對於修改記憶體位址的位置來說,就是 user 被替換。

那 immutable 和 Vue 的 watch 有什麼關係?

把上述的例子,拿來 vue 裡面放,就像這樣,也許會放在 vuex 不過意思差不多就是這樣。

有個 data 裡面有一個深一點的資料結構,也許是 object 也許是 array (在此用 object 當例子)

export default {
  data () {
    return {
      user {
        name: 'Chris',
        age: 18
      }
    }
  }
}

Vue.js 可以監聽的範圍,基本上只要是 return {} 這一層裡面的變數 watch 都可以正常連動。以這個例子來說,就是修改 user 就可以正常連動,但是有時候我們只是想要修改 age 怎辦?

就可以準備一個已經改好的物件,{ name: 'Chris', age: 19} 來替換掉 user 原本的物件就可以了。這樣一來,對 user 來說就不是修改,而是替換,也可以讓 vue.js 保證 watch 可以連動,不需要查詢這麼特別又複雜的使用方式。

export default {
  data () {
    return {
      user {
        name: 'Chris',
        age: 18
      }
    }
  },
  watch: {
    user() {
      console.log('update other data');
    }
  }
  methods: {
    updateUserAge() {
      this.user = {
        ...this.user,
        age: 19
      }
    }
  }
}

updateUserAge 就是一個簡單的做法,讓你在執行時, watch 的部份保證會連動到。Vue.js 會查覺到記憶體位址修改,所以要更新畫面。

Vue.js 的連動更新畫面

有時候,vuex 的資料結構太深,mutate 只修改成員變數時,也會造成無法正確觸發 getters 的問題,導致畫面沒有正確更新

const store = new Vuex.Store({
  state: {
    user: {
      name: 'Chris',
      age: 18
    }
  },
  mutations: {
    userName (state, { name }) {
      // 這樣可能 (我是說可能) 就會出現問題
      state.user.name = name
    }
  }
})

但是,只要保持 immutable 的觀念,讓更新。在官網的介紹中,叫 Reactivity Rules

const store = new Vuex.Store({
  state: {
    user: {
      name: 'Chris',
      age: 18
    }
  },
  mutations: {
    userName (state, { name }) {
      // 這樣一定可以更新
      state.user = {
        ...state.user,
        name
      }
    }
  }
})

參考資料

  1. 傳值?傳參?

上一篇
深入淺出 Computed
下一篇
Component 鬼牌(一): 看 props 決定 Component
系列文
Vue.js 進階心法30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Spark
iT邦新手 5 級 ‧ 2021-09-22 12:12:53

你好~想請教一下上述 user 物件的記憶體位置指派行為,如果我把 user.age 跟 user1.age都指派 19 ,那麼是兩個屬性上的記憶體位置都會指向 0xFF0408 嗎?

Chris iT邦新手 3 級 ‧ 2021-09-22 17:57:43 檢舉

由於 JavaScript 的規範 ECMA 並沒有規定 JavaScript 的實作方式。參考自深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?

這個問題可能會因為 JavaScript 引擎是 v8、Extreme 和 TraceMonkey 的不同,而所有不同。

我個人偏好使用 call by sharing 來解釋 JavaScript 的行為,所以如果都指派 19 ,就是會指向同一個 0xFF0408,就像都指派 'Chris' 一樣,都指向 0xFF0300

如果是另一種,看型別決定 call by value 還是 call by reference 的解釋,則會因為它是 primitive type 會直接寫在 user1.ageuser.age 裡,所以就不會共用同一個 19

Chris iT邦新手 3 級 ‧ 2021-09-24 08:24:49 檢舉

Spark 還是,其實是因為簡單型別,所以是不一樣的位址?

我要留言

立即登入留言