置路由出口組件 `<NuxtPage>` 完全指南:Props、頁面過渡與 Suspense 生命周期)
Nuxt 內(nèi)置路由出口組件NuxtPage完全指南Props、頁面過渡與 Suspense 生命周期【免費(fèi)下載鏈接】nuxtthe full-stack Vue framework項(xiàng)目地址: https://gitcode.com/GitHub_Trending/nu/nuxtNuxtPage是 Nuxt 框架內(nèi)置的路由出口組件用于渲染位于app/pages/目錄下的頂層或嵌套頁面是文件路由系統(tǒng)的核心渲染節(jié)點(diǎn)。本文基于當(dāng)前倉庫Nuxt 全棧 Vue 框架的官方 API 文檔與packages/nuxt/src/pages/runtime/page.ts源碼實(shí)現(xiàn)系統(tǒng)講解它的內(nèi)部結(jié)構(gòu)、全部 Props、頁面過渡與 keep-alive 配置方式、Suspense 下的生命周期差異、頁面實(shí)例引用獲取以及自定義 Props 透傳技巧讀完即可在應(yīng)用布局中正確地配置與調(diào)優(yōu)路由頁面渲染。為什么應(yīng)該使用NuxtPage而不是RouterView在 Nuxt 應(yīng)用中NuxtPage與 Vue Router 的RouterView組件職責(zé)相似——它們都是路由組件的出口決定當(dāng)前匹配的路由組件渲染在哪里。但官方文檔明確建議必須使用NuxtPage而不是直接使用RouterView。NuxtPage本質(zhì)上是RouterView的一層封裝它的額外價值在于負(fù)責(zé)維護(hù) Nuxt 內(nèi)部的頁面狀態(tài)例如頁面級 route 的響應(yīng)式派生、page:start/page:finish等生命周期 Hook 的觸發(fā)如果繞過它直接使用RouterView內(nèi)部狀態(tài)得不到正確處理可能導(dǎo)致useRoute()返回錯誤的路徑Nuxt 會自動掃描并渲染app/pages/目錄下的所有 Vue 組件因此在使用NuxtPage時無需手動傳入name與route它會由框架自動解析見頁面目錄文檔。源碼中的組件定義印證了這一點(diǎn)在 page.ts 中組件通過h(RouterView, { name: props.name, route: props.route, ...attrs }, ...)將用戶傳入的name與route原樣交給RouterView并通過自定義的插槽渲染邏輯接管了頁面組件的掛載方式。這也解釋了它為什么在 Props 類型上繼承了RouterViewProps。NuxtPage的內(nèi)部控制結(jié)構(gòu)從實(shí)現(xiàn)視角看NuxtPage在客戶端渲染的組件樹大致等效于下面這段模板僅示意實(shí)際經(jīng)組合式 API 與 VNode 構(gòu)建template RouterView v-slot{ Component } !-- 可選啟用頁面切換過渡時 -- Transition !-- 可選啟用頁面狀態(tài)保持時 -- KeepAlive Suspense component :isComponent / /Suspense /KeepAlive /Transition /RouterView /template這一嵌套層級對應(yīng)了源碼中的真實(shí)包裝順序在 page.ts 中可以看到最終渲染的 vnode 由_wrapInTransition(...)包裹wrapInKeepAlive(...)包裹Suspense組成而Suspense內(nèi)部再渲染RouteProvider來提供頁面級的響應(yīng)式 route。三個要點(diǎn)默認(rèn)不啟用Transition與KeepAlive。在 schema 的 app 配置 中app.pageTransition與app.keepalive的默認(rèn)值都是false。需要在三個層級中啟用它們nuxt.config全局配置、NuxtPage組件上的transition/keepaliveProps、頁面組件內(nèi)通過definePageMeta按頁配置。在頁面組件中啟用Transition時必須保證頁面模板只有一個根元素否則過渡動畫無法正確執(zhí)行。啟用 Transition 與 KeepAlive 的三種方式方式一全局配置nuxt.configexport default defineNuxtConfig({ app: { pageTransition: { name: page, mode: out-in }, keepalive: true, }, })app.pageTransition默認(rèn)值為falseapp.keepalive同樣默認(rèn)關(guān)閉見 packages/schema/src/config/app.ts。方式二在NuxtPage組件上按使用位置配置template NuxtPage transitionpage keepalive / /template方式三在頁面組件內(nèi)用definePageMeta單獨(dú)定義script setup langts definePageMeta({ key: route route.fullPath, transition: { name: page, mode: out-in }, keepalive: true, }) /script從源碼看這些來源之間存在明確的優(yōu)先級鏈。以 transition 為例在 page.ts 中const hasTransition !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)即NuxtPage的transitionProp 路由記錄的meta.pageTransition來自definePageMeta 全局默認(rèn)app.pageTransition。隨后這些配置通過_mergeTransitionProps合并定義于 packages/nuxt/src/app/components/utils.ts并在onAfterLeave回調(diào)里觸發(fā)page:transition:finishHook。keepalive 的解析邏輯類似見源碼第 184 行const routeKeepaliveConfig props.keepalive ?? routeProps.route.meta.keepalive ?? defaultKeepaliveConfig另外源碼中還處理了一個重要細(xì)節(jié)如果某些頁面通過definePageMeta開啟了 keep-alive當(dāng)導(dǎo)航到未開啟 keep-alive 的頁面時Nuxt 會把已開啟頁面組件的名稱累積到keepAliveInclude集合中并注入到有效的KeepAlive配置的include列表中從而保證切換路由時已緩存頁面不被清空對應(yīng) issue #33610 的修復(fù)。這一邏輯就實(shí)現(xiàn)在 page.ts 的shouldAugmentInclude分支中。Suspense 下的頁面生命周期差異NuxtPage在底層使用Suspense包裝頁面因此頁面切換時組件的生命周期行為與典型 Vue 應(yīng)用不同在典型 Vue 應(yīng)用中新頁面組件會在舊頁面完全卸載之后才被掛載在 Nuxt 中由于 VueSuspense的實(shí)現(xiàn)機(jī)制新頁面組件會在舊頁面卸載之前就被掛載。這一差異主要影響同時觀察舊頁面卸載與新頁面掛載兩個生命周期的代碼例如在onUnmounted/onMounted中執(zhí)行清理與初始化邏輯的場景編寫跨頁面共享狀態(tài)或動畫時需留意時序。源碼對該機(jī)制做了額外加固快速連續(xù)導(dǎo)航時組件會通過遞增suspenseKey重新掛載 Suspense 邊界僅在已成功 resolve 過一次之后避免未 resolve 的 Suspense 被提前拆除導(dǎo)致父級組件掛起對應(yīng) issue #28425 / #34683。同時客戶端在初次 hydration 期間如果組件在 Suspense resolve 前被卸載例如布局切換會通過onBeforeUnmount中的done()確保 hydration 流程正常收尾。此外客戶端渲染分支還做了陳舊 vnode 復(fù)用處理當(dāng)導(dǎo)航導(dǎo)致某個NuxtPage暫時沒有匹配的子頁面組件時會優(yōu)先渲染舊的 vnode 直到新路由解析完成對于已經(jīng)卸載的 Suspense 邊界上遺留的陳舊 vnode則通過isStaleVNode判斷并丟棄避免 hydration 階段讀取空el報錯對應(yīng) issue #23232。Props 詳解NuxtPage的 Props 在源碼 page.ts 中有完整的類型聲明與運(yùn)行時定義匯總?cè)缦翽rop類型作用說明namestring告訴RouterView渲染匹配路由記錄components選項(xiàng)中對應(yīng)名稱的組件。配合命名視圖使用對應(yīng)nameview.vue的命名文件約定見頁面目錄文檔的 Named Views 一節(jié)routeRouteLocationNormalized所有組件都已解析完畢的路由位置對象pageKeystring或(route) string控制NuxtPage何時被重新渲染transitionboolean或TransitionProps為通過該NuxtPage渲染的所有頁面定義全局過渡keepaliveboolean或KeepAliveProps控制通過該NuxtPage渲染的頁面狀態(tài)保持運(yùn)行時類型校驗(yàn)與文檔一致transition接受Boolean/Objectkeepalive同樣接受Boolean/ObjectpageKey接受Function/String默認(rèn)null。除了顯式 PropsNuxt 會自動解析name與route因?yàn)轫撁嫦到y(tǒng)會掃描并渲染app/pages/目錄下所有 Vue 組件文件并將每個組件與對應(yīng)的路由記錄自動關(guān)聯(lián)起來。pageKey控制頁面組件的重新渲染pageKey用于控制NuxtPage何時重新渲染頁面組件。理解它最直接的方式是看示例。如果傳入一個恒定不變的 keyNuxtPage只會在首次掛載時渲染一次template NuxtPage page-keystatic / /template也可以基于當(dāng)前路由使用動態(tài) keyNuxtPage :page-keyroute route.fullPath /?? 官方文檔特別警告不要在這里使用$route對象因?yàn)樗鼤蓴_NuxtPage基于Suspense的頁面渲染機(jī)制可能引發(fā)渲染異常。除了組件上直接傳 ProppageKey也可以在頁面組件的script中通過definePageMeta以key字段傳入script setup langts definePageMeta({ key: route route.fullPath, }) /script關(guān)于pageKey的默認(rèn)行為可以從工具函數(shù) packages/nuxt/src/pages/runtime/utils.ts 的generateRouteKey中看出端倪當(dāng)沒有顯式傳入pageKey、路由也沒有meta.key時Nuxt 默認(rèn)基于路由匹配到的路徑將:param等動態(tài)段替換為實(shí)際參數(shù)值生成 key——這意味著默認(rèn)情況下同一個頁面組件在不同參數(shù)如/users/1與/users/2之間切換會被判定為不同 key 而觸發(fā)重渲染。用key: route route.fullPath顯式定義則可以讓 key 精確跟隨完整路徑。另外在客戶端當(dāng)pageKey發(fā)生變化時源碼會通過 watcher 觸發(fā)page:loading:startHook見 page.ts 第 83-89 行并在頁面 resolve 后依次觸發(fā)page:finish與page:loading:end從而實(shí)現(xiàn)與useLoadingIndicator加載進(jìn)度條的聯(lián)動。獲取頁面組件實(shí)例ref 與 pageRef由于NuxtPage內(nèi)部有多層包裝直接給NuxtPage綁ref拿到的并不是頁面組件本身而是NuxtPage組件實(shí)例。Nuxt 通過expose({ pageRef })將真正渲染的頁面組件實(shí)例暴露出來因此需要通過ref.value.pageRef訪問。script setup langts const page ref() function logFoo () { page.value.pageRef.foo() } /script template NuxtPage refpage / /template對應(yīng)的頁面組件需要把方法暴露出去才能被外部調(diào)用script setup langts const foo () { console.log(foo method called) } defineExpose({ foo, }) /script源碼實(shí)現(xiàn)中pageRef定義于setup中并通過expose({ pageRef })暴露同時通過RouteProvider的vnodeRef傳入并作為頁面 vnode 的ref綁定見 route-provider.ts 第 73 行h(props.vnode, { ref: props.vnodeRef })從而保證pageRef始終指向?qū)嶋H渲染的頁面組件實(shí)例。向頁面透傳自定義 PropsNuxtPage除了上述內(nèi)置 Props 外還接受任何自定義 Props并會把它們繼續(xù)向下傳遞到頁面組件。例如在布局入口傳入一個自定義 Propfoobartemplate NuxtPage :foobar123 / /template在頁面組件中可以通過defineProps正常接收script setup langts const props defineProps{ foobar: number }() console.log(props.foobar) // 輸出: 123 /script如果頁面組件沒有用defineProps聲明該 Prop仍然可以通過attrsuseAttrs()拿到透傳值script setup langts const attrs useAttrs() console.log(attrs.foobar) // 輸出: 123 /script這在實(shí)現(xiàn)布局統(tǒng)一注入頁面公共參數(shù)如頁面標(biāo)題 key、分區(qū)標(biāo)識等時非常實(shí)用。值得一提的是源碼中NuxtPage設(shè)置了inheritAttrs: false并在渲染時把除內(nèi)置 Props 外的attrs原樣展開傳給RouterViewh(RouterView, { ..., ...attrs }, ...)再經(jīng)由插槽與RouteProvider傳遞到頁面 vnode這正是自定義 Props 能夠一路透傳到頁面組件的底層原因。源碼中的配套實(shí)現(xiàn)與測試圍繞NuxtPage當(dāng)前倉庫的源碼與測試形成了完整的印證鏈條組件主實(shí)現(xiàn)packages/nuxt/src/pages/runtime/page.ts —— 涵蓋 Props 聲明、Suspense包裝、transition/keepalive 合并與優(yōu)先級、suspenseKey重掛載策略、page:start/page:finish/page:loading:end等 Hook 觸發(fā)、hydration 期間錯誤 Hook 的注冊等。路由 key 與 KeepAlive 工具packages/nuxt/src/pages/runtime/utils.ts ——generateRouteKey默認(rèn) key 推導(dǎo)與pageKey覆蓋、wrapInKeepAlive。頁面級響應(yīng)式 route 提供者packages/nuxt/src/app/components/route-provider.ts ——RouteProvider通過provide(PageRouteSymbol, ...)向頁面提供派生自當(dāng)前渲染分叉的 route并承擔(dān)pageRef綁定。過渡合并工具packages/nuxt/src/app/components/utils.ts ——_mergeTransitionProps與_wrapInTransition。全局默認(rèn)配置packages/schema/src/config/app.ts ——app.pageTransition: false與app.keepalive: false的默認(rèn)值。端到端測試test/nuxt/nuxt-page.test.ts —— 覆蓋不同嵌套深度路由下NuxtPage的掛載行為、setup/render 次數(shù)統(tǒng)計(jì)等可作為理解其生命周期語義的補(bǔ)充材料該測試文件超過 1100 行還包含多層級嵌套與異步 setup 場景的回歸用例。若需要進(jìn)一步了解頁面文件到路由的映射關(guān)系、命名視圖nameview.vue約定以及definePageMeta的全部可用字段可繼續(xù)閱讀 頁面目錄文檔 與 definePageMeta 工具文檔?!久赓M(fèi)下載鏈接】nuxtthe full-stack Vue framework項(xiàng)目地址: https://gitcode.com/GitHub_Trending/nu/nuxt創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考