Composables 是 Vue 3 Composition API 的重要概念,將共用的有狀態邏輯(stateful logic)寫成一個可重用的函式以提高可維護性與測試性,在 Nuxt 3 使用時並無須手動引入(Auto-imports 👍)。
Nuxt 3 預設有多組內建的 Composables,詳細內容可參考官方文件,以下舉幾個常見的例子。
提供了一種 Nuxt 執行時存取 context 的方法,在伺服器端與客戶端都可以使用,且幫助我們存取 Vue 實體、Hooks、設定值與其他內部狀態等。
// app.vue
const nuxtApp = useNuxtApp()
console.log(nuxtApp)

provide (name, value)// app.vue
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('Kitty'))
Server-Side

Client-Side

提供如同 document.cookie 的功能,可以讓 Web 伺服器記住使用者的狀態資訊(ex:登入者的 token、購物車或喜愛商品)。
// app.vue
<template>
<div>
<h1>Counter: {{ counter }}</h1>
<button @click="counter = null">reset</button>
<button @click="counter--">-</button>
<button @click="counter++">+</button>
</div>
</template>
// app.vue
<script setup lang="ts">
const counter = useCookie('counter')
counter.value = 0
</script>
瀏覽器畫面顯示。

已建立名稱為 counter 的 cookie,且值為 0。

useCookie('counter') 表使用名稱為 counter 的 cookie。 但須注意此處不代表就建立 counter 的 cookie,而是需要 counter.value = 0,賦值之後才會建立,如果少了這段,counter-- 或 counter++ 會出現錯誤 NAN。

如果希望在 useCookie 直接加上預設值也是可以的,只需在後面加上 default 值。
const counter = useCookie('counter', {
default: () => 0,
})
清除 cookie 只需 counter = null,cookie 就會被刪除。
⚠ 注意:官方文件有說明 "useCookie only works during setup or Lifecycle Hooks.",如果在自定義的 composables/ 或其他地方使用 useCookie,重新渲染畫面可能會出現 cookie 回到預設值的問題。
此處僅稍微帶過 useCookie 的使用方式,關於如何設定 cookie 的 maxAge、httpOnly、domain ... 等,可以參考 useCookie。
🌞 其他還有像 useFetch、useRoute、useState ... 等常用且重要的 composables 未來會陸續出現在每一天的文章中

如果要建立自訂的 Compsables 可以參考以下步驟。
建立 composables/ 資料夾。
建立 Composables:
方法一:使用具名導出
// composables/useMouseX.ts
export const useMouseX = () => {
const x = useState('x', () => 0)
const Update = (event: any) => {
x.value = event.pageX
}
onMounted(() => window.addEventListener('mousemove', Update))
onUnmounted(() => window.removeEventListener('mousemove', Update))
return { x }
}
方法二:使用預設導出
// composables/useMouseY.ts
export default function () {
const y = useState('y', () => 0)
const Update = (event: any) => {
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', Update))
onUnmounted(() => window.removeEventListener('mousemove', Update))
return { y }
}
使用時 Nuxt 會自動轉換成該檔名的 camelCase 形式(不包含副檔名),例如上述範例若將 useMouseY.ts 改為 use-mouseY.ts,使用時仍然是 useMouseY()。
現在可以在 .js、.ts 和 .vue 檔案中使用自動引入的 Composables。
//app.vue
<template>
<h1>游標座標</h1>
<h2>x: {{ x }}</h2>
<h2>y: {{ y }}</h2>
</template>
// app.vue
<script setup lang="ts">
const { x } = useMouseX()
const { y } = useMouseY()
</script>

Nuxt 只會掃描在 composables/ 目錄下的頂層檔案,範例如下:
composables 掃描
┣ index.ts ✅
┣ useCounter.ts ✅
┗ mouse
┣ useMouseX.ts ❌
┗ useMouseY.ts ❌
⭐ Nuxt 3 提供兩個方式使得在巢狀結構內的 composables 也可以被 Nuxt 掃描:
(推薦)在 composables/index.ts 重新導出:
// composables/index.ts
export { useMouseX } from './mouse/useMouseX.ts'
修改 nuxt.config.ts 中的設定:
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
dirs: [
// 掃描頂層模組
'composables',
// 掃描特定名稱和副檔名的第一層模組
'composables/*/index.{ts,js,mjs,mts}',
// 或掃描指定目錄中的所有模組
'composables/**'
]
}
})
今天提到的 Composables 是將有狀態邏輯寫成一個可重用的函式,那麼我們該如何建立共用的無狀態邏輯(Stateless Logic)函式?該存放在哪個資料夾中呢?明日揭曉!