Nuxt 3 預設的自動載入 (Auto Imports),其中包含了 composables 與 utils 目錄,這兩個目錄通常會用來定義一些專案中常重複使用的輔助函式或實用的程式,兩個目錄的載入與掃描方式基本上是一致的,在實務上我們也會特別依據函式或常用程式的特性或用途來放置到不同的目錄。
根據 Vue 官方文件說明,組合式函式 (Composables) 通常是一種利用 Vue 3 的組合式 API (Composition API) 來封裝和複用具有狀態邏輯 (stateful logic) 的函式。
舉個例子,建立一個檔案 ./composables/useCounter.js 如下程式碼:
export default function () {
const count = ref(0)
const increment = () => {
count.value += 1
}
return {
count,
increment
}
}
Nuxt 3 自動導入的特性,我們就能在其他元件中使用 useCounter 這個組合式函式。
<template>
<div>
<span>Count: {{ count }}</span>
<button @click="increment">
+1
</button>
</div>
</template>
<script setup>
const { count, increment } = useCounter()
</script>
得利於 Vue 3 的組合式 API (Composition API),同一個邏輯的功能與狀態,可以放置在同一個區塊,不僅易讀也更好維護,更可以封裝成組合式函式來進行複用。從程式碼可以發現,當我們使用到 useCounter 組合式函數,都會回傳一個具有響應狀態的 count,這個 count 會因為呼叫了 increment 而數值增加 1,而具有狀態的回傳與相關操作,這類函式我們可以將它歸類為具有狀態邏輯 (stateful logic) 的函式。
隨著專案的增長許多邏輯或程式可能會重複利用到,你可能會為此建立函式來重複使用這些函式,然而這個函式可能只是一個格式化文字或日期的方法,這個函式期望接收一些輸入並回傳輸出。
建立一個檔案 ./utils/toLocaleStringFromUnixTime.js 如下程式碼:
export default function (unixTime) {
return new Date(unixTime*1000).toLocaleString(undefined, {hour12:false})
}
上面這個用來格式化日期的函式,它接受一個 Unix 時間輸入並回傳預期的日期輸出,當一個函式每次都能保持相同輸入得到相同的輸出,並且不會對函式以外的作用域產生副作用,可以稱之為純函式 (Pure Function),純函式符合著無狀態,可以預期的特型,所以將一個純函式或無狀態邏輯的函式,與組合式函式 (composibles) 做出區分,所以在 Nuxt 的 utils 目錄來讓專案共通使用這個無狀態邏輯 (stateless logic) 函式是合理的,如果你有聽過或使用過 lodash 和 date-fns,它們也是屬於無狀態邏輯的函式庫,用來提供給專案使用這些可以共通使用的函式。
通用函式通常定義在專案的 ./utils 目錄下,而在專案目錄下的通用函式,僅有在客戶端 Vue 中可以做使用,意思就是你無法在 Nuxt 的 Server API 中使用這些通用函式,因為 Nuxt 的 Server API 使用的自動導入函式,應該定義在 ./server/utils
目錄下。
可以發現到組合式函式的導出,通常是以 use
為開頭,例如 useState
、useNuxtApp
等,當你看到 use
開頭的函數,多數情況你可以視它為就是一個組合式函式,同理,我們在自訂共用的函式時,若為組合式函式通常也會以 use
為開頭的駝峰式 (Camel Case) 命名法來命名,算是一個約定俗成的做法。
在建立組合式函式時,它可以接受 ref 或 getter 作為參數,建議會將接受的響應式狀態轉換為原始數值,即使你的函式邏輯內不依賴這些狀態來實現反應性也應該如此,你可以透過 Vue 提供的 toValue()
函式來將響應式狀態標準化轉換為原始數值。
import { toValue } from 'vue'
function useFeature(maybeRefOrGetter) {
const value = toValue(maybeRefOrGetter)
}
在呼叫使用組合式函式,通常會約定所接收到的回傳具有多個響應式狀態,那麼會建議使用物件來包含這些 ref,而不是使用 reactive。
function useMouse() {
const x = ref(0)
const y = ref(0)
return {
x,
y
}
}
因為這樣在呼叫使用的時候,可以對其進行解構,同時保留反應性
:
// app.vue
const { x, y } = useMouse()
你也可以使用 reactive 包裝回傳的物件,以便解構參考:
const mouse = reactive(useMouse())
<template>
Mouse position is at: {{ mouse.x }}, {{ mouse.y }}
</template>
在組合式函式內是可以執行一些具有副作用的操作,但請注意下列幾點
如果您正在開發使用伺服器端渲染 (Server Side Rendering, SSR) 的應用程式,請確保在元件 Mount 後才呼叫生命週期 hook 中所執行操作 DOM 、瀏覽器才有的 API 等相關副作用,例如 onMounted()
僅會在瀏覽器中做呼叫,以確保可以訪問到 DOM。
如果組合式函式內添加了 DOM 的事件監聽器,則需要記得使用 onUnmounted()
來建立移除監聽器的流程。當然你也可以使用像 VueUse 提供或自訂一個 useEventListene 組合式函式,來建立監聽器並在未使用時自動的移除監聽器。
// useEventListener.js
import { onMounted, onUnmounted } from 'vue'
export default function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
根據上面的介紹稍微整理一下建立的時機:
在開發一些自訂函式時,你也可以先找找網路上是否已經有人設計分享過,個人推薦讀者可以導入 VueUse,VueUse 提供了許多實用組合式函式,讓你在開發過程中可以加快生產效率。
在 Nuxt 3 的專案目錄下,composables 與 utils 目錄都具有自動導入的特性,可以用來建立提供給 Nuxt 應用程式重複使用的共用函式,推薦你可以依據函式的特性做分類與命名,在後續維護與,在建立這些函式之前,也可以參考網路上的函式庫避免重複造輪子。
感謝大家的閱讀,歡迎大家給予建議與討論,也請各位大大鞭小力一些:)
如果對這個 Nuxt 3 系列感興趣,可以訂閱
接收通知,也歡迎分享給喜歡或正在學習 Nuxt 3 的夥伴。
參考資料