Skip to main content

Command Palette

Search for a command to run...

Vue 3: Composable

Updated
2 min read

甚麼是composable?
在寫純JavaScript的時候,有時會想將一些重複的邏輯抽離出來變成另一個可被其他程式碼使用的函式,在Vue3裡面,composables也是在做一樣的事情;只是跟傳統函式不一樣的是,composables內可以包含Vue的狀態,譬如響應式狀態變數refreactive等,或者lifecycle hooks像是onMountedonUnmounted等。

基本用法

根據官方文件Composable有幾個使用的注意事項:

  1. 習慣上是以use開頭的小駝峰式(camelCase)命名;

  2. 基本上只能在<script setup>setup()頂層呼叫(但也有特別提到某些情況下可以在lifecycle hooks裡面呼叫);

  3. 習慣上,composable永遠回傳純物件(plain object),而不是響應式物件,而這個純物件裡面可包含多個響應式狀態refs;另外,若想以物件取值的方式取得這些refs的狀態,可將composable回傳的純物件放入一個 reactive

    const mouse = reactive(useMouse())
    // mouse.x is linked to original ref
    console.log(mouse.x)
    
  4. composable裡面可以呼叫其他的composables;

  5. composable裡面可以有副作用(side effect),譬如一些DOM事件監聽或是異步拉取遠端資料(fetching data),但使用時需要注意的是:

    • Server-Side Rendering (SSR)應用,需要確保DOM相關的副作用是在post-mount lifecycle hooks 如onMounted()裡面被呼叫(因為這些hooks只會在瀏覽器內運作)。另外千萬要記得在onUnmounted()裡面清除這些DOM相關的副作用。

Watch composables’ arguments

如果要”監聽”傳入composable的參數,要在composable裡面使用 watchEffect 去監聽引數,並且在Vue 3.3以後版本要讓composable只接收 ref 或getter函式而不是一般的純值作為參數,以及搭配 toValue API去取得值:

// 官方範例 (applicable to Vue 3.3 or later)

import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  const fetchData = () => {
    // reset state before fetching..
    data.value = null
    error.value = null

    fetch(toValue(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error }
}

如果是Vue 3.2以前的版本則是要接收refs並改用 unRef 去取得值並搭配 isRef 判斷是否要監聽函式:

// 改寫自官方範例 (applicable to Vue 3.2)

import { ref, isRef, unref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  const fetchData = () => {
    // reset state before fetching..
    data.value = null
    error.value = null
    // unref() unwraps potential refs
    fetch(unref(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err))
  }

  if (isRef(url)) {
    // setup reactive re-fetch if input URL is a ref
    watchEffect(fetchData)
  } else {
    // otherwise, just fetch once
    // and avoid the overhead of a watcher
    fetchData()
  }

  return { data, error }
}

不過官方有提到如果這個composable要給其他開發者使用,建議假設composable的參數都是以 ref 或getter函式來處理。

使用Composables的優點

  1. composables不一定要在需要抽離重複邏輯的時候才使用,有時候一個Vue程式碼太過複雜時,也能透過composable抽離裡面的邏輯,幫忙整理程式碼,有助於程式碼的可讀性。

    Composables can be extracted not only for reuse, but also for code organization.

  2. 相較使用renderless components會有額外較重的元件實體(instance)呼叫成本,composables就是單純用函式去包住需要被隔離出來的程式碼邏輯。所以說,當有需要同時抽離程式碼和視覺上的邏輯可以使用renderless components,若只抽離單純的程式碼邏輯就優先建議使用composables。


References

2 views

More from this blog

Vite: vite-plugin-html的bugs和坑

因為工作要用Vite去生成一個mpa專案且根據頁面不同,動態生成不同 <title> 的HTML,所以就找了一個套件──vite-plugin-html,結果因為套件好像很久沒維護,隨著Vite升級又多了一些有的沒的bug,順手來記錄一下。 template delete 像我第一個就是遇到生成的時候template HTML也會被刪掉,後來看到issue上也有人反映,還好有好心人士解決這個問題,

Feb 26, 20261 min read4

Lun's Blog

82 posts

Notes for web development