文章目的
公司未來專案開發要逐漸轉向使用 Nuxt3,因此利用此文章記錄 Nuxt3 的學習。
前言
撰寫這篇文章時,Nuxt3 還是 Beta 階段,很多新奇潮到爆的技術,但也因此可能會跟 bug 不期而遇,或是遇到部分工具、套件不相容問題,待未來版本越來越成熟,相依套件、工具一定會越來越多,奇怪的 bug 當然相對減少。
此篇文章會記錄一些我在使用 Nuxt3 時遇到的問題,或是如何引入一些我們以前常用的工具。
介紹
Nuxt3 是基於 Vue3 的 SSR 框架,同時 default 支援 TypeScript,在打包處理上也做了相當多的優化,像是引入 Vite 等,相較 Nuxt2 對於專案的瘦身與效能都有相當程度的提升。
更多 Nuxt3 的特色我就不多加介紹,如果有興趣可以去看看官方文件。
資料夾結構
主要介紹幾個與 Nuxt2 規則不同的資料夾。
pages
如果你有用過 Nuxt2,對這資料夾應該不陌生,但在 Nuxt3 中 pages 是選用的,原因是如果專案只用到 app.vue
這隻檔案做開發,就不需要 pages,減少專案大小。
但較複雜的專案我們還是會需要 pages 來幫助我們產生不同路徑的頁面,基本上 pages 的結構跟 Nuxt2 沒什麼差異,唯一需要注意的是動態路由的命名從以前的下底線變成中括號,例如 _id.vue => [id].vue
。
另外 Nuxt3 還提供了一種與 RouterView
相同作用的 component - NuxtChild
,在 component 中直接調用 <NuxtChild />
會有一樣的效果。
components
Nuxt3 會自動幫我們引入 components 底下的元件,因此我們調用元件只需要根據元件的資料夾結構調用即可,例如:
1 | | components/ |
引入時 tag 名稱就會是 <BaseFooButton />
,但這邊會建議將你的元件命名跟調用的名稱一致,意思是將 Button.vue
變更成 BaseFooButton.vue
會是更好的選擇,不用擔心重複的文字因為 Nuxt3 會自己幫我們刪除。
components 現在可以透過加入前綴 Lazy
,達到元件延遲載入的效果,使用情境通常會是該元件並不需要馬上出現,而是在特定時機出現,就可以透過 Lazy 的方式提升效能,寫法像是 <LazyComponentName />
。
composables
這是 Nuxt3 新增的資料夾,這個資料夾專門用來管理 Vue3 的 composition api,而且它會自動 import 裡面的檔案,不需要我們手動處理。
composition api 可參考。
layouts
Nuxt3 初始是沒有 layouts 資料夾的,當建立 layouts 資料夾並新增 default.vue
時,所有的頁面都會預設使用 default layout,記得要在 layout 裡使用 slot 做注入才能正確渲染畫面。
1 | <template> |
plugins
Nuxt3 plugins 資料夾底下的檔案一樣會自動幫我們 import,另外在命名時增加 .server
或 .client
前綴,就可以讓 plugin 在對應端引入,像是 plugin.client.js
。
在 plugins 的檔案中起手式需要透過 nuxtApp
對其做對應操作,可以看看下面的程式碼:
1 | import { defineNuxtPlugin } from '#app'; |
我們也可以為 nuxtApp
寫上 Vue3 提供的 provide
寫法做全局注入:
1 | import { defineNuxtPlugin } from '#app'; |
接著我們就可以在任何地方做全局的調用:
1 | <script setup> |
另外,plugins 也可以像以前 Nuxt2 一樣引入我們裝的相關套件,寫法只有些微變化:
1 | import { defineNuxtPlugin } from '#app' |
新用法
Nuxt3 重新定義了一些新方法,或是將舊有的 Nuxt2 方法重新包裝,我們來看看改變了什麼?
目前我還沒嘗試過所有新方法,以下僅列出我有嘗試過的方法做分享,若有興趣可以參考官方文件,未來有接觸更多方法,我會再補上。
State
Nuxt3 新增了一個 useState
的方法,如果你有寫過 React hook 對這個名字應該不陌生。
在 Nuxt3 中 useState
可以說是一個輕量版的 vuex,它可以幫我們管理 global state,並隨時做取用、更改。
useState
的優勢在於在 server 端時會將 state 的值作保留並沿用到 client 端,寫法也很簡易、取用方便。
缺點方面,在我目前看來,是有點太過簡易了,改變值的方法沒有統一的規範,容易造成不同頁面用不同方法去改變 state,管理上可能會有瑕疵。
useState
創立時基本上只需要提供一個唯一的 key,並且賦予它 init 的值即可。
接下來看看範例:
1 | // useState 僅能作用在 setup 或是其他生命週期中 |
上面的範例中,我們宣告了一個 counter 的 useState
,接著這個 state 就可以在任一個 component 透過 useState('counter')
一起共享這個狀態。
若要更改 state 的值也很簡單,可以直接針對它做處理,甚至直接賦值,像是:
1 | <template> |
相信看了改變 state 的方法,你應該就懂我的疑慮了,state 的改變過於簡單、容易,在比較複雜的專案上可能會導致管理不易。
但在較小的專案上利用 useState
處理一些 global 的狀態,會是一個替代 vuex 很好的選擇。
除了直接在 setup
定義外,也可以將 useState
的定義全部放在 composables 這個資料夾做管理,來看看範例吧:
1 | // 將所有的 state 放在 state.js 這隻檔案中 |
NuxtApp
如果你有用過 Nuxt2 相信你對 context
這個參數不陌生,在 Nuxt 2 中我們透過 context
取得我們的 nuxtApp。
在 Nuxt3 中重新定義了這個參數,我們透過 useNuxtApp
來取得所謂的 context
,我們趕緊來看看範例:
1 | import { useNuxtApp } from '#app'; |
nuxtApp 可以在 composables、components 還有 plugins 取用,跟 Nuxt2 一樣,在 plugins 中會當作第一個參數做代入。
若你需要註冊一些全局方法,可以利用 provide
做註冊,若與 Nuxt2 做對應就是 inject
:
1 | const nuxtApp = useNuxtApp(); |
Data Fetching
Nuxt3 新增了幾個 fetch 功能,這些功能可以讓我們在不需要安裝其他 api 套件的情況下實踐 fetch 資料。
提供的 fetch 功能如下:
useAsyncData
useLazyAsyncData
useFetch
useLazyFetch
使用它們的時機一樣是只有在 setup
或是其他生命週期。
useAsyncData
useAsyncData
是這四個功能裡最基本的一個,另外三個都可以說是它的衍生,我們來看看它的用法吧:
1 | const { data, pending, refresh, error } = useAsyncData(key, fn, options); |
傳入參數:
- key: 確保此 fetch 唯一,避免與其他 request 重疊。
- fn: return 非同步函數的值。
- options:
- lazy: Boolean,true 的話調用此方法,在進入 router 時並不會因為還在 fetch 而 pending,而是直接進入 router。
- default: 在 fetch 完成之前,設定預設 data,通常會搭配
lazy: true
做使用。 - server: Boolean,是否在 server 端 fetch 資料,預設為 true。
- transform: 函數,用來更改 fn 回傳的結果。
- pick: Array,fetch 結果只取用寫在 array 裡的 key。
回傳結果:
- data: fetch response 的資料。
- pending: Boolean,告訴是否還在 fetch。
- refresh: 用來強制刷新 data 的函數。
- error: 當 response 失敗時,產生的 object。
useLazyAsyncData
這方法是 useAsyncData
+ lazy: true
的組合,其他的參數與回傳的值都會與 useAsyncData
相同。
useFetch
它是 useAsyncData
與 $fetch
的結合,並且它也不需要 key,key 會自動根據 url 與選項幫你產生。
因此傳入此方法的參數只有兩個,一個是 url,另一個是 options。
1 | <script setup> |
你一定會問說 $fetch
是啥?這是套件 ohmyfetch 的方法,useFetch
簡單來講就是把這套件包裝進去。
如果專案 fetch 資料的需求沒有很複雜的話,可以直接用包好的
useFetch
來處理 CRUD。
但如果有一定複雜度,目前不建議這樣用,原因是 ohmyfetch 這套件還很新,很多功能沒有很成熟。
跟 useAsyncData
相比,它的 options 多了 method
、params
、headers
、baseURL
可以提供。
這四個選項都是 ohmyfetch 的選項,有興趣可以看看文件。
useLazyFetch
這方法是 useFetch
+ lazy: true
的組合,其他的參數與回傳的值都會與 useFetch
相同。
套件使用
這邊會介紹幾個套件如何在 Nuxt3 中使用,並解釋它們帶來的功用。
axios
如果你是一個有經驗的前端開發者,我相信你絕對對這套件不陌生。
axios 應該是現在 fetch 資料,最熱門的套件之一,我們就來看看如何在 Nuxt3 中使用 axios 吧。
透過 yarn add axios
或 npm install axios
為專案安裝 axios,接著我們就可以在任何需要 fetch 資料的 component 引入:
1 | <script> |
這邊可以看到用了前面提到的 useAsyncData
來調用 axios,至於為什麼要這麼做呢?
因為 Nuxt3 的 useAsyncData
其實跟 Nuxt2 的 asyncData
很像,在這裡調用可以幫助我們在 server 端也可以正確 fetch 資料。
這邊要注意的是 useAsyncData
的 fn 參數必須回傳一個值出來。
用上面的方法調用 axios,在較大專案時會很難管理我們 fetch 的方法。
既然都用 Nuxt3 了,我們就來嘗試看看用 composition api 管理 axios 吧!
在 composables
底下創建一隻 api.js
(命名可以調整):
1 | import axios from 'axios'; |
我們透過 composition api 幫我們管理創建出來的 axios 實例,接著只需要在 component 中呼叫這個實例即可:
1 | <script> |
再進階一點我們可以針對 axios 的方法做封裝:
1 | import axios from 'axios'; |
1 | <script> |
每次呼叫我們還需要用 then
去回傳我們的 data,既然這樣我們就把它一起放入封裝方法中:
1 | import axios from 'axios'; |
這樣子呼叫時寫法就更簡潔了:
1 | <script> |
最後你也可以將每個 api 分類管理,例如:
1 | // 路徑 composables > useUserApi.js |
axios 的管理方法非常多元,以上分享我整理的管理方法並與 Nuxt3 做結合以供參考。
pinia
如果你寫過 Vue 的專案,相信你對 vuex 不陌生,vuex 是全局 state 的管理工具。
這邊要介紹的是 pinia,它是更為輕量的全局管理工具,跟 vuex 相比寫法也更加簡潔。
目前 Nuxt3 官方也推薦使用 pinia 作為 state 處裡的套件,pinia 在 SSR 這方面有足夠的支援,讓我們免除一些安全性問題。
讓我們來看看 pinia 的使用吧。
在終端機輸入 yarn add pinia @pinia/nuxt
或是 npm install pinia @pinia/nuxt
將套件安裝至專案中。
接著在 nuxt.config.ts
引入:
1 | import { defineNuxtConfig } from 'nuxt3' |
我們可以在專案創建一個 stores
資料夾,專門放我們創建的 store
。
現在命名一個 useCounter
的 js,接著來創建 store
吧:
1 | import { defineStore } from 'pinia' |
透過 defineStore
創立一個新的 store
,並且在第一個參數賦予它一個命名,state
則是透過 function return 的方式建立。
接著在需要使用到的 component 中引入:
1 | <script> |
當你在 component 中使用 store 時,store 都是帶有 composition api 的 reactive
屬性。
若我們使用解構的方式獲得 store 裡的 state 會讓響應性失效,因此可以利用 pinia 的 storeToRefs
來幫助我們維持 state 的響應性:
1 | import { storeToRefs } from 'pinia'; |
同時我們可以透過定義 actions
幫助我們操作 store 的 state,讓其符合商業邏輯:
1 | import { defineStore } from 'pinia' |
在 component 中可以很輕鬆的取得 actions:
1 | <script> |
以上是 Nuxt3 引入 pinia 以及簡單的使用方法,其他方法可以參考官方文件。