[Vue] 有樣學樣的 Hook

去年年底比較大的新聞大概就是 React 推出了 Hook 的功能。不過因為我沒有寫 React,所以到底他在幹嘛其實我也不太清楚(欸)!然後 Vue 這邊真的就只是實驗性功能而已,請不要亂上 Production!


關於 React Hook 可以參考這個影片介紹,

個人理解若有錯誤煩請指正感恩 XD

然後 Vue 的老爸也做了一套 Hook,看起來就是向 React 致敬的感覺(連裡面的名字都差不多)。

這個東西目前還是實驗性功能,所以官方並不推薦在正式環境中使用。

實際上看過原始碼之後,其實 實作的部分 一整個不到 200 行,相當簡潔的做法。主要的核心大概是這三個部分,

  • withHook 傳入函式,然後返回一個 Vue 包裝過的實例物件。
  • hook 利用 Vue.mixin 將 Hook 的實例物件注入 beforeCreatebeforeMount 生命週期中。
  • currentInstance 用以紀錄實際上 Hooks 生效的 Vue 實例物件。

然後,vue-hook 本身所提供的方法有,

  • useState
  • useEffect
  • useRef
  • useMounted
  • useDestroyed
  • useUpdated
  • useWatch
  • useComputed

你說這不是致敬,什麼才是致敬

稍微熟悉 Vue Component 的人可能會覺得,原本生命週期中,其實也有很類似的 Lifecycle Hooks,例如,

  • mounted
  • watch
  • computed
  • updated
  • destroyed

看起來很像,但是實際上應用的方式並不同。因為這個 Hooks 並不需要引入一整個 Vue 實例物件,當中也不需要處理 this 這種很曖昧的事情,所以雖然字面上雷同,不過操作的面向卻完全不一樣。

簡單來說,他很類似 React Hooks API 所能做到的事情(好像是廢話)。之所以會說不需要引入一整個 Vue 實例物件,我們來偷學一下 React 的部分,然後接地氣換成 Vue 的做法來看看,

或者你也可以看官方提供的範例 https://codesandbox.io/s/jpqo566289

通常我們如果要做一個 Vue Component 並且使用它,我們大概會這樣寫。

import Vue from 'vue'
import App from '@/components/app'

new Vue({
  el: '#app',
  render: h => h(App)
})

然後 App 裡面我們會處理一些樣板的事情,

<template>
  <p>Count is: {{ count }}</p>
  <button type="button" @click="addCount()">+</button>
</template>

<script>
export default {
  name: 'app',
  methods: {
    addCount () {
      this.count++
    }
  },
  data () {
    return {
      count: 0
    }
  },
  watch: {
    count (newVal) {
      console.log('Count is: ' + newVal)
    }
  }
}
</script>

如果只是一個簡單的工作,或是,專案之中並不需要引入整個 Vue 的生態的話,那麼 Hooks 就能提供相當好的解法,

import Vue from 'vue'
import { withHooks, useState, useWatch, useMounted, useComputed } from 'vue-hook'

const App = withHooks(h => {
  const [data, setCount] = useState({ count: 0 })
  
  const doubleCount = useComputed(() => data.count * 2)
  
  useMounted(() => {
    console.log('Mounted!')
  })

  useWatch(() => count, (newVal, oldVal) => {
    console.log('Count is: ' + newVal)
  })

  return h('div', [
    h('p', `Count is: ${data.count}`),
    h(
      'button',
      {
        on: {
          click: () => setCount(data.count + 1)
        }
      },
      '+'
    )
  ]);
})

new Vue({
  el: '#app',
  render (h) {
    return h('div', [h(App)])
  }
})

不過,畢竟不建議導入正式環境,所以目前還是觀望一下好了。這樣的寫法可以解決部分動態載入預設值會出現 undefined 的狀況,或是 Vue Components 實例物件尚未載入時,可以用這些 Hooks 來預先處理一些事情,感覺也是挺不錯的。我覺得比較大的好處是,可以不用處理那些曖昧不明的 this 算是相當感動的事情(哭)。


那麼這個跟 Vuex 所提供的東西是不是很類似?
爾或者是,我自己宣告一個 Vue 來當 XXXBus 放東西就好啦(例如 EventBus)?
噢!如果我用全域的 Vue.mixin 應該也可以吧?

嗯,你要這麼想也是可以,但剛剛提到了,他本身不需要 Vue 實例物件,而 Vuex 是必須放在 Vue Components 裡面使用,這是最大的差異。或是把一些事情丟去給 Vue.mixin 做,實際上也可以,但是整個操作的過程與生命週期跟 Hooks 還是完全不一樣的。

至於說你再額外 New 一個 Vue 實例出來用,也不是不可以,但操作的方式就會挺類似 Vuex,而且搞不好更難用(你得確認你做的方法與實例能夠比 Vuex 好才可以)。而且,除非是 EventBus,不然實在不確定這樣做會有什麼好處?

既然說到 EventBus,目前提出來的 Hooks 主要目的之一,是可以解決元件之間的溝通問題。不過,我想對於父子元件之間,目前是看不出來有什麼有效的溝通方式(起碼在 vue-hook 當中並無特別強調)。不過對於同級元件來說,Hooks 確實也提供了一個,不需要 Vuex 的溝通方式,而且可以讓整個 Vue Component 代碼更函示化一點(應該啦)。

總而言之,對於 Vue 來說都是實驗性功能,請不要用在正式環境。
如果你心臟很大顆,麻煩用過之後跟我分享一下 XD

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