透過 100 多個技巧的集合來學習 Nuxt!

useNuxtApp

存取 Nuxt 應用程式的共用執行階段上下文。

useNuxtApp 是一個內建的 composable,提供一種存取 Nuxt 共用執行階段上下文的方式,也稱為 Nuxt 上下文,它在客戶端和伺服器端都可用(但不在 Nitro 路由中)。它可以幫助您存取 Vue 應用程式實例、執行階段鉤子、執行階段配置變數和內部狀態,例如 ssrContextpayload

app.vue
<script setup lang="ts">
const nuxtApp = useNuxtApp()
</script>

如果您的作用域中執行階段上下文不可用,則調用 useNuxtApp 時會拋出例外。您可以改用 tryUseNuxtApp 用於不需要 nuxtApp 的 composable,或者只是檢查上下文是否可用,而不會拋出例外。

方法

provide (name, value)

nuxtApp 是一個執行階段上下文,您可以使用 Nuxt 外掛模組 來擴展它。使用 provide 函數創建 Nuxt 外掛模組,以使值和輔助方法在您的 Nuxt 應用程式中的所有 composable 和組件中都可用。

provide 函數接受 namevalue 參數。

const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)

// Prints "Hello name!"
console.log(nuxtApp.$hello('name'))

正如您在上面的範例中所看到的,$hello 已成為 nuxtApp 上下文的新的自訂部分,並且在所有可以存取 nuxtApp 的地方都可用。

hook(name, cb)

nuxtApp 中可用的鉤子允許您自訂 Nuxt 應用程式的執行階段方面。您可以在 Vue.js composable 和 Nuxt 外掛模組 中使用執行階段鉤子來鉤入渲染生命週期。

hook 函數適用於通過在特定點鉤入渲染生命週期來添加自訂邏輯。hook 函數主要在創建 Nuxt 外掛模組時使用。

請參閱 執行階段鉤子 以了解 Nuxt 呼叫的可用的執行階段鉤子。

plugins/test.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:start', () => {
    /* your code goes here */
  })
  nuxtApp.hook('vue:error', (..._args) => {
    console.log('vue:error')
    // if (import.meta.client) {
    //   console.log(..._args)
    // }
  })
})

callHook(name, ...args)

當使用任何現有的鉤子調用時,callHook 返回一個 promise。

await nuxtApp.callHook('my-plugin:init')

屬性

useNuxtApp() 公開了以下屬性,您可以使用這些屬性來擴展和自訂您的應用程式,並共享狀態、資料和變數。

vueApp

vueApp 是全域 Vue.js 應用程式實例,您可以通過 nuxtApp 存取它。

一些有用的方法

  • component() - 如果同時傳遞名稱字串和組件定義,則註冊全域組件;如果僅傳遞名稱,則檢索已註冊的組件。
  • directive() - 如果同時傳遞名稱字串和指令定義,則註冊全域自訂指令;如果僅傳遞名稱,則檢索已註冊的指令(範例)
  • use() - 安裝一個 Vue.js 外掛模組 (範例)
https://vuejs.org/api/application.html#application-api 中閱讀更多內容。

ssrContext

ssrContext 在伺服器端渲染期間生成,並且僅在伺服器端可用。

Nuxt 通過 ssrContext 公開以下屬性

  • url (字串) - 當前請求 URL。
  • event (unjs/h3 請求事件) - 存取當前路由的請求和回應。
  • payload (物件) - NuxtApp payload 物件。

payload

payload 將伺服器端的資料和狀態變數公開給客戶端。以下鍵在從伺服器端傳遞後將在客戶端上可用

  • serverRendered (布林值) - 指示回應是否為伺服器端渲染。
  • data (物件) - 當您使用 useFetchuseAsyncData 從 API 端點獲取資料時,可以從 payload.data 存取結果酬載。此資料會被快取,並協助您在多次發出相同請求時防止重複獲取相同的資料。
    <script setup lang="ts">
    const { data } = await useAsyncData('count', () => $fetch('/api/count'))
    </script>
    

    在上述範例中使用 useAsyncData 獲取 count 的值後,如果您存取 payload.data,您會看到 { count: 1 } 記錄在那裡。
    當從 ssrcontext 存取相同的 payload.data 時,您也可以在伺服器端存取相同的值。
  • state (物件) - 當您在 Nuxt 中使用 useState composable 來設定共享狀態時,此狀態資料會透過 payload.state.[您的狀態名稱] 存取。
    plugins/my-plugin.ts
    export const useColor = () => useState<string>('color', () => 'pink')
    
    export default defineNuxtPlugin((nuxtApp) => {
      if (import.meta.server) {
        const color = useColor()
      }
    })
    

    也可以使用更進階的類型,例如 refreactiveshallowRefshallowReactiveNuxtError
    Nuxt v3.4 起,您可以為 Nuxt 不支援的類型定義自己的 reducer/reviver。
    觀看 Alexander Lichter 關於序列化酬載的影片,尤其是關於類別的部分。

    在以下範例中,我們為 Luxon DateTime 類別定義了一個 reducer(或序列化器)和一個 reviver(或反序列化器),使用了 payload plugin。
    plugins/date-time-payload.ts
    /**
     * This kind of plugin runs very early in the Nuxt lifecycle, before we revive the payload.
     * You will not have access to the router or other Nuxt-injected properties.
     *
     * Note that the "DateTime" string is the type identifier and must
     * be the same on both the reducer and the reviver.
     */
    export default definePayloadPlugin((nuxtApp) => {
      definePayloadReducer('DateTime', (value) => {
        return value instanceof DateTime && value.toJSON()
      })
      definePayloadReviver('DateTime', (value) => {
        return DateTime.fromISO(value)
      })
    })
    

isHydrating

使用 nuxtApp.isHydrating (布林值) 來檢查 Nuxt 應用程式是否正在客戶端進行 hydration。

components/nuxt-error-boundary.ts
export default defineComponent({
  setup (_props, { slots, emit }) {
    const nuxtApp = useNuxtApp()
    onErrorCaptured((err) => {
      if (import.meta.client && !nuxtApp.isHydrating) {
        // ...
      }
    })
  }
})

runWithContext

您很可能因為收到「Nuxt instance unavailable」訊息而來到這裡。請謹慎使用此方法,並回報導致問題的範例,以便最終可以在框架層級解決問題。

runWithContext 方法旨在用於呼叫函式並給予它明確的 Nuxt context。通常,Nuxt context 會隱式地傳遞,您無需擔心這個問題。但是,當在 middleware/plugins 中使用複雜的 async/await 情境時,您可能會遇到在非同步呼叫之後,目前實例已被取消設定的情況。

middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
  const nuxtApp = useNuxtApp()
  let user
  try {
    user = await fetchUser()
    // the Vue/Nuxt compiler loses context here because of the try/catch block.
  } catch (e) {
    user = null
  }
  if (!user) {
    // apply the correct Nuxt context to our `navigateTo` call.
    return nuxtApp.runWithContext(() => navigateTo('/auth'))
  }
})

用法

const result = nuxtApp.runWithContext(() => functionWithContext())
  • functionWithContext:任何需要目前 Nuxt 應用程式 context 的函式。此 context 將會自動正確地套用。

runWithContext 將會回傳 functionWithContext 所回傳的任何內容。

Context 的更深入解釋

Vue.js Composition API(以及 Nuxt composables 類似地)透過依賴隱式 context 來運作。在生命週期中,Vue 會將目前組件的臨時實例(以及 Nuxt nuxtApp 的臨時實例)設定為全域變數,並在相同的 tick 中取消設定。當在伺服器端渲染時,會有來自不同使用者的多個請求,並且 nuxtApp 在相同的全域 context 中執行。因此,Nuxt 和 Vue 會立即取消設定此全域實例,以避免在兩個使用者或組件之間洩漏共享參考。

這意味著什麼?Composition API 和 Nuxt Composables 僅在生命週期期間以及任何非同步操作之前的相同 tick 中可用。

// --- Vue internal ---
const _vueInstance = null
const getCurrentInstance = () => _vueInstance
// ---

// Vue / Nuxt sets a global variable referencing to current component in _vueInstance when calling setup()
async function setup() {
  getCurrentInstance() // Works
  await someAsyncOperation() // Vue unsets the context in same tick before async operation!
  getCurrentInstance() // null
}

解決此問題的經典方法是在第一次呼叫時將目前實例快取到像 const instance = getCurrentInstance() 這樣的區域變數中,並在下一個 composable 呼叫中使用它,但問題是任何巢狀的 composable 呼叫現在都需要明確地接受實例作為參數,而不是依賴 composition-api 的隱式 context。這是 composables 的設計限制,本身不是問題。

為了克服這個限制,Vue 在編譯我們的應用程式碼時做了一些幕後工作,並在每次 <script setup> 呼叫後還原 context。

const __instance = getCurrentInstance() // Generated by Vue compiler
getCurrentInstance() // Works!
await someAsyncOperation() // Vue unsets the context
__restoreInstance(__instance) // Generated by Vue compiler
getCurrentInstance() // Still works!

有關 Vue 實際執行的操作的更佳描述,請參閱 unjs/unctx#2 (comment)

解決方案

這就是可以使用 runWithContext 來還原 context 的地方,類似於 <script setup> 的運作方式。

Nuxt 內部使用 unjs/unctx 來支援類似於 Vue 的 plugins 和 middleware 的 composables。這使得像 navigateTo() 這樣的 composables 可以在不直接將 nuxtApp 傳遞給它們的情況下工作 - 將 Composition API 的 DX 和效能優勢帶到整個 Nuxt 框架。

Nuxt composables 的設計與 Vue Composition API 相同,因此需要類似的解決方案來神奇地進行這種轉換。查看 unjs/unctx#2 (提案)、unjs/unctx#4 (轉換實作) 和 nuxt/framework#3884 (整合到 Nuxt)。

Vue 目前僅支援 <script setup> 的非同步 context 還原,用於 async/await 用法。在 Nuxt 3 中,新增了對 defineNuxtPlugin()defineNuxtRouteMiddleware() 的轉換支援,這表示當您使用它們時,Nuxt 會自動使用 context 還原轉換它們。

剩餘問題

用於自動還原 context 的 unjs/unctx 轉換在包含 awaittry/catch 語句中似乎有錯誤,這最終需要解決,以便消除上述建議的變通方法的必要性。

原生非同步 Context

使用新的實驗性功能,可以使用 Node.js AsyncLocalStorage 和新的 unctx 支援來啟用原生非同步 context 支援,以使非同步 context 原生 地提供給任何巢狀非同步 composable,而無需轉換或手動傳遞/使用 context 呼叫。

原生非同步 context 支援目前在 Bun 和 Node 中運作。
文件 > 指南 > 進階 > 實驗性功能#asynccontext 中閱讀更多資訊。

tryUseNuxtApp

此函式的作用與 useNuxtApp 完全相同,但如果 context 不可用,則會回傳 null,而不是拋出例外。

您可以將其用於不需要 nuxtApp 的 composables,或僅用於檢查 context 是否可用,而無需例外。

使用範例

composable.ts
export function useStandType() {
  // Always works on the client
  if (tryUseNuxtApp()) {
    return useRuntimeConfig().public.STAND_TYPE
  } else {
    return process.env.STAND_TYPE
  }
}