為何選Nuxt.js?
在前后端分離出現(xiàn)之前,傳統(tǒng)的web頁(yè)面都是服務(wù)端渲染的,如JSP、PHP、Python Django,還有各種模板技術(shù)Freemarker, velocity,thymeleaf、mustache等等。其實(shí)這套技術(shù)都挺成熟的,也用了很多年。
但前后端分離出現(xiàn)后,帶來(lái)兩個(gè)好處:
- 工程上的分工,讓前端專門(mén)盯前端技術(shù),開(kāi)發(fā)效率上得到提升
- 通過(guò)各種CDN, nodejs技術(shù),前端的性能能持續(xù)優(yōu)化,部署方式更為靈活多變,帶來(lái)更多的想象空間
vue系的Nuxt.js 和 React系的Next.js誰(shuí)更勝一籌?目前我還沒(méi)有答案,貌似后者更成熟一些,但Nuxt也發(fā)展迅速。
此外,阿里的UmiJs也在冉冉升起…
理解Nuxt3工作模式的關(guān)鍵: 渲染機(jī)制
寫(xiě)Nuxt3代碼時(shí)一個(gè)最大的困惑就是:你寫(xiě)的這段代碼是運(yùn)行在客戶端還是服務(wù)端的?是運(yùn)行在哪個(gè)階段的?。因?yàn)镹uxt3的渲染機(jī)制模糊了客戶端和服務(wù)端的邊界。而且,有些頁(yè)面是在構(gòu)建階段生成的。
注意:Nuxt3默認(rèn)是開(kāi)啟ssr的(在nuxt.config.js里可以關(guān)閉它),默認(rèn)它采用一致性渲染(Universal rendering)。
開(kāi)啟ssr后,即使你/pages目錄下的代碼,如axios請(qǐng)求或$fetch請(qǐng)求,Nuxt3也會(huì)在服務(wù)端執(zhí)行,在服務(wù)端取回?cái)?shù)據(jù)。如果你在請(qǐng)求中調(diào)用了客戶端的API如sessionStorage,則會(huì)報(bào)錯(cuò)。這點(diǎn)要非常小心。
Nuxt3支持的渲染機(jī)制
Nuxt3提供了幾種不同的按需渲染機(jī)制:
-
CSR:Client Side Rendering:僅客戶端渲染(CSR):頁(yè)面由JS在瀏覽器里動(dòng)態(tài)生成:js通過(guò)ajax從后臺(tái)取數(shù)據(jù),動(dòng)態(tài)生成DOM
-
SSR: server-side rendering通用渲染(SSR): 頁(yè)面由服務(wù)端nodejs生成: nodejs將vue代碼解析,一次性生成html返回給瀏覽器。帶來(lái)的好處是搜索引擎優(yōu)化(SEO)。
-
SSG: static site generation
-
ISR:Incremental Static Regeneration,vue不支持,但nuxt3支持,需要node提供
-
ESR: Edge Side Rendering,核心思想是,借助邊緣計(jì)算的能力,將靜態(tài)內(nèi)容與動(dòng)態(tài)內(nèi)容以流式的方式,先后返回給用戶。cdn 節(jié)點(diǎn)相比于 server,距離用戶更近,有著更短的網(wǎng)絡(luò)延時(shí)。在 cdn 節(jié)點(diǎn)上,將可緩存的頁(yè)面靜態(tài)部分,先快速返回給用戶,同時(shí)在 cdn 節(jié)點(diǎn)上發(fā)起動(dòng)態(tài)部分內(nèi)容請(qǐng)求,并將生成的靜態(tài)部分的緩存后,繼續(xù)返回給用戶。
-
SWR:stale-while-revalidate,一種由 HTTP RFC 5861(opens in a new tab) 推廣的 HTTP 緩存失效策略。這種策略首先從緩存中返回?cái)?shù)據(jù)(過(guò)期的),同時(shí)發(fā)送 fetch 請(qǐng)求(重新驗(yàn)證),最后得到最新數(shù)據(jù)。使用 SWR,組件將會(huì)不斷地、自動(dòng)獲得最新數(shù)據(jù)流。UI 也會(huì)一直保持快速響應(yīng)。參見(jiàn):https://swr.vercel.app/zh-CN
-
混合渲染:不同的路由,不同的頁(yè)面,采用不同的渲染機(jī)制。Hybrid Rendering 機(jī)制:
Hybrid rendering allows different caching rules per route using Route Rules and decides how the server should respond to a new request on a given URL.
所以,我們可以在nuxt config里為不同的route配置不同的渲染策略,分別有:redirect、ssr、cors、headers和static and swr 幾種選項(xiàng)。
Nuxt3提供了HMR(Hot Module Replacement)熱更新機(jī)制。
Nuxt的渲染流程
此外,別的渲染機(jī)制還有NSR(Native Side Rendering),它首先在列表頁(yè)中加載離線頁(yè)面模板,通過(guò) Ajax 預(yù)加載頁(yè)面數(shù)據(jù),通過(guò) Native 渲染生成 Html 數(shù)據(jù)并且緩存在客戶端。
重要概念:模塊、中間件和插件
- module:模塊提供了一種比較好的擴(kuò)展和復(fù)用機(jī)制。模塊能對(duì)外提供vue組件,組合API,插件。模塊通過(guò)defineNuxtModule()定義。
- layer:具有nuxt.config 和 app.config配置和標(biāo)準(zhǔn)nuxt目錄結(jié)構(gòu)的軟件包,方便被其它項(xiàng)目復(fù)用:在nuxt.config.ts中可以通過(guò)extend一個(gè)layer來(lái)引用layer
- middleware:攔截路由切換,每次切換都會(huì)調(diào)用中間件的邏輯
- plugin: 插件能方便地引入第三方的功能部件,然后掛到nuxtApp,作為全局對(duì)象使用
所以: 公共的、獨(dú)立的功能可以封裝成Composable,也可以封裝成Plugin。
export default defineNuxtPlugin((nuxtApp) => {
const instance = ofetch.create({
baseURL: '/api',
headers: {
Accept: 'application/json'
}
})
// You can also just do this instead of returning
// nuxtApp.provide('ofetch', instance)
return {
provide: {
ofetch: instance
}
}
})
然后,可以這么用:
const { $ofetch } = useNuxtApp()
路由中間件
路由中間件能在前端路由切換時(shí)做一些事情。分為頁(yè)面級(jí)和全局兩種。全局中間件文件以.global.ts結(jié)尾。
中間件可以定義在插件或頁(yè)面中,也可以作為一個(gè)單獨(dú)的文件放在middleware目錄下。
在插件中定義一個(gè)中間件:
export default defineNuxtPlugin(() => {
addRouteMiddleware('global-test', () => {
console.log('this global middleware was added in a plugin')
}, { global: true })
addRouteMiddleware('named-test', () => {
console.log('this named middleware was added in a plugin')
})
})
在頁(yè)面中定義中間件(inline middleware):
<template>
<div>
Forbidden
</div>
</template>
<script setup>
definePageMeta({
// This is an example of inline middleware
middleware: () => {
console.log('Strictly forbidden.')
return false
}
})
</script>
在頁(yè)面中使用中間件:
<script setup>
definePageMeta({
middleware: 'redirect-me'
})
</script>
當(dāng)從其它頁(yè)面路由到該頁(yè)面時(shí),redirect-me中間件會(huì)被調(diào)用。
在路由中間件中可以返回一個(gè)頁(yè)面,例如:
export default defineNuxtRouteMiddleware((to, from) => {
return 'test/main'
})
則應(yīng)用該中間件的頁(yè)面會(huì)直接跳轉(zhuǎn)到pages/test/main.vue頁(yè)面。如果不寫(xiě)return語(yǔ)句,則進(jìn)入該頁(yè)面。
Nuxt3工程結(jié)構(gòu)
package.json參考
{
"name": "mall",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"serve": "nuxt dev",
"preview": "nuxt preview",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxt/content": "^1.0.0",
"@nuxtjs/pwa": "^3.3.5",
"core-js": "^3.25.3",
"element-plus": "^2.2.27",
"@element-plus/icons-vue": "^2.0.10",
"vue": "3.2.45",
"pinia": "^2.0.14",
"@pinia/nuxt": "^0.4.5"
},
"devDependencies": {
"nuxt": "^3.0.0",
"nuxt-windicss": "^2.5.5",
"vite": "^3.2.4",
"@nuxt/types": "^2.15.8",
"@nuxt/typescript-build": "^2.1.0",
"@iconify/vue": "^3.2.1",
"@vueuse/nuxt": "^8.4.2",
"@windicss/plugin-animations": "^1.0.9",
"sass": "^1.51.0"
}
}
目錄結(jié)構(gòu)
首要的一點(diǎn):要分清哪些是客戶端代碼,哪些是服務(wù)端代碼。
我們?cè)趕erver下面的代碼可以理解為運(yùn)行在服務(wù)端,這些API充當(dāng)代理轉(zhuǎn)發(fā)的作用,以解決我們前端跨域問(wèn)題,就和nginx里的proxypass轉(zhuǎn)發(fā)給后端一樣的作用。Nuxt 3 內(nèi)部采用Nitro server作為服務(wù)器,Nitro 用unjs/h3 這個(gè)框架內(nèi)部處理請(qǐng)求和路由。
約定成俗的目錄結(jié)構(gòu):
├── app.vue # Nuxt 3 應(yīng)用程序中的主組件 入口組件
├── components # 組件目錄,支持自動(dòng)導(dǎo)入
├── layouts # 布局目錄
├── composables # 公共函數(shù),支持自動(dòng)導(dǎo)入
├── assets # 靜態(tài)資源目錄 與vue項(xiàng)目的assets相同
├── middleware # 路由中間件
├── nuxt.config.ts # Nuxt 配置文件,可以理解成vue.config.js 文件名必須是nuxt.config 后綴名可以是.js,.ts或.mjs
├── package.json
├── pages # 基于文件的路由
├── plugins #插件
├── public # 不會(huì)參與打包,與vue項(xiàng)目的public類似直接掛在服務(wù)器的根目錄
├── README.md
├── server
├── tsconfig.json
└── yarn.lock
注意:
- composables下如果有嵌套目錄,則需要在嵌套目錄下放置index.ts,再在里面export相應(yīng)對(duì)象。
- components組件嵌套在目錄內(nèi),可以用駝峰式引入,如引入components/user/avatar.vue,可以用<UserAvatar>
- server目錄,下面可以有api,middleware, plugin等子目錄,api下面每個(gè)文件對(duì)應(yīng)一個(gè)restful API,好像沒(méi)法一個(gè)文件定義多個(gè)API。
自動(dòng)導(dǎo)入
Nuxt3支持自動(dòng)導(dǎo)入(auto-import),也就是說(shuō)在composables、components等目錄下的對(duì)象,可以直接在vue組件里使用。
在server端,~server/utils目錄下的對(duì)象也是能被server端代碼自動(dòng)導(dǎo)入的。
頁(yè)面之間的關(guān)系
入口點(diǎn)在app.vue中:
<script setup>
import { ID_INJECTION_KEY } from "element-plus";
provide(ID_INJECTION_KEY, {
prefix: 100,
current: 0,
});
</script>
<template>
<div>
<NuxtLayout>
<NuxtLoadingIndicator :height="5" :duration="3000" :throttle="400" />
<NuxtPage />
</NuxtLayout>
</div>
</template>
通過(guò)<NuxtPage>找到layout下某個(gè)layout,可以通過(guò)名稱指定layout,缺省是layouts/default.vue:
<!--default.vue文件-->
<template>
<main class="py-2 px-10 text-center">
<slot />
<Footer />
<div class="mt-5 mx-auto text-center opacity-25 text-sm">
</div>
</main>
</template>
也可以在page中通過(guò)definePageMeta()宏指定layout。
然后,default.vue中的slot會(huì)被route里指定的某個(gè)page替換,默認(rèn)的page是pages/index.vue。
在page里就可以調(diào)用我們?cè)赾omponents目錄下存放的各個(gè)組件了。
<template>
<div>
<Header />
<PageWrapper>
hello world
</PageWrapper>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const activeIndex = ref('1')
const activeIndex2 = ref('1')
const handleSelect = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const value1 = ref();
</script>
<style></style>
上面這個(gè)page用到了兩個(gè)組件:Header和PageWrapper,分別對(duì)應(yīng)components下的Header/index.vue和page/Wrapper.vue文件。
這就是從app.vue->layout->page->component的調(diào)用關(guān)系。
頁(yè)面跳轉(zhuǎn)與參數(shù)傳遞
頁(yè)面跳轉(zhuǎn)
- 可以采用<NuxtLink>
- 調(diào)用navigateTo()函數(shù)
<script setup>
const router = useRouter();
const name = ref('');
const type = ref(1);
function navigate(){
return navigateTo({
path: '/search',
query: {
name: name.value,
type: type.value
}
})
}
</script>
- useRouter.push()函數(shù)
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
function test() {
router.back();
router.forward();
router.go();
router.push({ path: "/home" });
router.replace({ hash: "#bio" });
}
</script>
注意:const router = useRouter() 需要放在setup()函數(shù)里。不能直接放到方法里,否則router為空。
動(dòng)態(tài)路由傳參
- 通過(guò)動(dòng)態(tài)路由/user/[id]/info請(qǐng)求頁(yè)面,在該頁(yè)面中通過(guò)route.params.id可以獲取參數(shù)id。
- 通過(guò)navigateTo()或router.push()路由,則在頁(yè)面中通過(guò)route.query.name和route.query.type能獲得相應(yīng)參數(shù)。
狀態(tài)共享和持久化
采用useState()在頁(yè)面之間共享狀態(tài)
Nuxt3提供useState組合式函數(shù),使用此函數(shù)可以創(chuàng)建一個(gè)可在整個(gè)組件中共享的狀態(tài),此狀態(tài)還是響應(yīng)式的并且對(duì)于SSR非常友好。
之所以是SSR友好的,是因?yàn)槿绻诜?wù)端使用useState保存狀態(tài)的話,此狀態(tài)會(huì)在服務(wù)端渲染后序列化并發(fā)送到客戶端,這樣共享狀態(tài)可以在客戶端的所有組件中使用。
注意,useState只能在setup和lifecycle Hooks中使用。
page1.vue:
<template>
{{ counter }}
<el-button @click="counter++">加1</el-button>
<el-button @click="counter--">減1</el-button>
</template>
<script setup lang="ts">
const counter = useState("counter", () => Math.round(Math.random() * 1000)) // 定義并初始化
</script>
page2.vue:
<template>
{{ counter }}
</template>
<script setup lang="ts">
const counter = useState("counter") // 引用已存在的對(duì)象
</script>
例如,我們可以在多個(gè)頁(yè)面之間共享當(dāng)前登錄用戶信息。
用Pinia共享狀態(tài)和持久化
yarn add -D @pinia-plugin-persistedstate/nuxt
然后,配置nuxt.config.ts:
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
],
})
再然后:
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {
return {
someState: 'hello pinia',
}
},
persist: true,
})
持久化可以選擇
persist: {
storage: persistedState.localStorage,
},
或者
persist: {
storage: persistedState.sessionStorage,
},
幾個(gè)主要命令
- nuxt 啟動(dòng)一個(gè)熱加載的 Web 服務(wù)器(開(kāi)發(fā)模式) localhost:3000。
- nuxt build 利用 webpack 編譯應(yīng)用,壓縮 JS 和 CSS 資源(發(fā)布用)。
- nuxt start 以生產(chǎn)模式啟動(dòng)一個(gè) Web 服務(wù)器 (需要先執(zhí)行nuxt build)。
- nuxt generate 編譯應(yīng)用,并依據(jù)路由配置生成對(duì)應(yīng)的 HTML 文件 (用于靜態(tài)站點(diǎn)的部署)。
主要參數(shù):
–config-file 或 -c: 指定 nuxt.config.js 的文件路徑。
–spa 或 -s: 禁用服務(wù)器端渲染,使用 SPA 模式
–unix-socket 或 -n: 指定 UNIX Socket 的路徑。
動(dòng)態(tài)組件
使用vue中動(dòng)態(tài)組件的寫(xiě)法要使用resolveComponent語(yǔ)法:
<template>
<div>
<component :is="isHeader ? TheHeader : 'div'" />
</div>
</template>
<script setup>
const isHeader = ref(1)
// 組件
const TheHeader = resolveComponent('TheHeader')
</script>
后臺(tái)交互:$fetch()和useFetch()
Nuxt3 不再需要axios這個(gè)模塊了,直接采用內(nèi)置的useFetch, useLazyFetch, useAsyncData and useLazyAsyncData幾個(gè)方法。我們通常在server/api下的模塊中使用這些API。
$fetch是Nuxt3對(duì)ofetch的封裝. 在服務(wù)器端渲染期間,調(diào)用$fetch獲取內(nèi)部API 路由將直接調(diào)用相關(guān)函數(shù)(模擬請(qǐng)求),節(jié)省額外的 API 調(diào)用。請(qǐng)注意,$fetch是Nuxt 3中進(jìn)行 HTTP請(qǐng)求的首選方式,而不是為 Nuxt 2 進(jìn)行的@nuxt/http和@nuxtjs/axios。
useFetch()封裝了useAsyncData和$fetch, 它會(huì)根據(jù)URL和fetch選項(xiàng)自動(dòng)生成key,并推斷API響應(yīng)類型。默認(rèn)情況下,useFetch 會(huì)阻止導(dǎo)航,直到它的異步處理程序被解析。
const { users } = await $fetch('/api/users', { method: 'POST', body: { some: 'json' } })
// Adding baseURL
await $fetch('/config', { baseURL })
// Adding params
await $fetch('/movie?lang=en', { params: { id: 123 } })
await useFetch(() => "/my/post/url", {
method: 'POST',
mode: 'cors', // 允許跨域
body: { some: true },
initialCache: false,
onResponse({ request, response, options }) {
// Process the response data
},
});
也可以這么寫(xiě): (這種寫(xiě)法不推薦,不要在vue component中直接向后端發(fā)送請(qǐng)求,要通過(guò)API中轉(zhuǎn))
onMounted(async () => {
const{ data, pending, error, refresh } = await useFetch(() => 'http://localhost:8888/cms/api/ebook/listall', {mode: 'cors'}, { immediate: true })
const bookList = JSON.parse(data.value) // 注意data是一個(gè)vue的ref對(duì)象,需要.value獲得其值
bookList.forEach(book => {
console.log(book.bookName)
});
})
在server/api程序defineEventHandler()中,可以直接返回JSON 數(shù)據(jù),一個(gè) Promise 或者使用 event.res.end() 來(lái)發(fā)送響應(yīng)。
$fetch()的第二個(gè)參數(shù)options的類型為FetchOptions
interface FetchOptions<R extends ResponseType = ResponseType> extends Omit<RequestInit, "body"> {
baseURL?: string;
body?: RequestInit["body"] | Record<string, any>;
params?: SearchParameters;
query?: SearchParameters;
parseResponse?: (responseText: string) => any;
responseType?: R;
response?: boolean;
retry?: number | false;
onRequest?(context: FetchContext): Promise<void> | void;
onRequestError?(context: FetchContext & {
error: Error;
}): Promise<void> | void;
onResponse?(context: FetchContext & {
response: FetchResponse<R>;
}): Promise<void> | void;
onResponseError?(context: FetchContext & {
response: FetchResponse<R>;
}): Promise<void> | void;
}
傳遞的參數(shù)通過(guò)body, 或params或query來(lái)設(shè)置。
useFetch和useAsyncData的區(qū)別
- useFetch接收一個(gè) URL并獲取該數(shù)據(jù),而useAsyncData可能有更復(fù)雜的邏輯。useFetch(url)幾乎等同于useAsyncData(url, () => $fetch(url))。 useFetch是useAsyncData的封裝
- useAsyncData是最常見(jiàn)用例的開(kāi)發(fā)人員體驗(yàn)糖。useAsyncData,做一些簡(jiǎn)單的get數(shù)據(jù)請(qǐng)求,useFetch可以做更復(fù)雜的post、put、delete等請(qǐng)求
<script>
await useAsyncData(() => $fetch(`/api/hello/${ctr.value}`), { watch: [ctr] })
</script>
最關(guān)鍵的一點(diǎn)是:useAsyncData()里的操作是在服務(wù)端執(zhí)行的,如果要SSR,請(qǐng)把a(bǔ)xios或fetch請(qǐng)求數(shù)據(jù)的操作放在useAsyncData()里。
useAsyncData與useLazyAsyncData的區(qū)別
- useLazyAsyncData是useAsyncData的lazy:true的封裝。
- useLazyAsyncData是異步函數(shù),不會(huì)阻塞導(dǎo)航, 但是pending時(shí)它的初始值為null, 開(kāi)始的時(shí)候不能立馬訪問(wèn),可以通過(guò)watch監(jiān)聽(tīng)拿到數(shù)據(jù)
useFetch在onMounted()中的使用
UseFetch()在onMounted中使用,需要進(jìn)行延遲調(diào)用才可以使用,否則獲取不到數(shù)據(jù)。解決方式是使用nextTick()方法。
直接調(diào)用獲取不了數(shù)據(jù):
onMounted(() => {
const { data } = await useFetch('/api/ebook/getall')
bookList.value = data.value
})
需要:
onMounted(() => {
nextTick(async () => {
const { data } = await useFetch('/api/ebook/getall')
bookList.value = data.value
})
})
mounted鉤子函數(shù)執(zhí)行時(shí)所有的DOM掛載和渲染都已完成,如果數(shù)據(jù)變化后要執(zhí)行某個(gè)操作,而這個(gè)操作需要改變DOM結(jié)構(gòu)的時(shí)候,這個(gè)操作都應(yīng)該放進(jìn)Vue.nextTick()的回調(diào)函數(shù)中。
使用axios
Nuxt3下也可以繼續(xù)使用axios庫(kù),集成方式比較簡(jiǎn)單,寫(xiě)一個(gè)插件,expose出axios實(shí)例:
import axios from "axios";
export default defineNuxtPlugin((nuxtApp) => {
const defaultUrl = "<https://localhost:5001>";
let api = axios.create({
baseUrl: defaultUrl,
headers: {
common: {},
},
});
return {
provide: {
api: api,
},
};
});
然后就可以調(diào)用this$.api的方法了。
服務(wù)端引擎Nitro
Nitro 的基礎(chǔ)是 rollup 和 h3:一個(gè)為高性能和可移植性而生的最小 http 框架。
在nuxt3中的新服務(wù)端引擎 Nitro Engine, nuxt2中服務(wù)端核心使用的是connect.js,而nuxt3使用的是nuxt團(tuán)隊(duì)自研的h3框架,特點(diǎn)就是具有很強(qiáng)的可移植性,而且非常輕量級(jí),并且還支持connect編寫(xiě)的中間件。也就是說(shuō)nuxt3基于h3編寫(xiě)的server端,可以無(wú)縫地移植到支持js運(yùn)行環(huán)境的地方。
Nuxt3開(kāi)發(fā)團(tuán)隊(duì)在 Nuxt 的新服務(wù)端引擎 Nitro 上工作了整整 9 個(gè)月。它解鎖了 Nuxt 服務(wù)端等方面新的全棧能力 。
在開(kāi)發(fā)中,它使用 rollup 和 Node.js workers 來(lái)為服務(wù)端代碼和上下文隔離服務(wù)。并且通過(guò)讀取 server/api/ 目錄下的文件和 server/functions 目錄下的服務(wù)端函數(shù)來(lái)生成你的服務(wù)端 API。
在生產(chǎn)中,它將您的 app 和服務(wù)端代碼構(gòu)建到獨(dú)立的 .output 目錄中。這份輸出是很輕量的:代碼是壓縮的,并且移除了所有 Node.js 模塊。你可以在任何支持 JavaScript 的系統(tǒng)下部署這份產(chǎn)物,Node.js、Severless、Workers、邊緣渲染(Edge Side Rendering)或純靜態(tài)部署。
這份產(chǎn)物包含了運(yùn)行時(shí)代碼,來(lái)支持在任意環(huán)境下運(yùn)行 Nuxt 服務(wù)端(包括實(shí)驗(yàn)性的瀏覽器 Service Workers?。┑模⑶覇?dòng)靜態(tài)文件服務(wù),這使得它成為了一個(gè)符合 JAMStack 架構(gòu)的真正的 hybrid 框架。另外還實(shí)現(xiàn)了一個(gè)原生存儲(chǔ)層,支持多個(gè)源、驅(qū)動(dòng)和本地資源。
Server端的寫(xiě)法
如果你的頁(yè)面要SEO,那取數(shù)據(jù)就得經(jīng)過(guò)server層。不能直接在page里ajax找后臺(tái)要數(shù)據(jù)。而是需要server層的轉(zhuǎn)發(fā)。
Nuxt自動(dòng)掃描~/server/api, ~/server/routes, 和 ~/server/middleware目錄中的文件,以注冊(cè)具有HMR支持的API和服務(wù)器處理程序。
每個(gè)文件都應(yīng)該導(dǎo)出一個(gè)用defineEventHandler()定義的默認(rèn)函數(shù)。處理程序可以直接返回JSON數(shù)據(jù),一個(gè)Promise或使用event.node.res.end()發(fā)送響應(yīng)。
上下文對(duì)象nuxtApp
useNuxtApp()返回一個(gè)nuxtApp實(shí)例主要是提供了一個(gè)可以訪問(wèn)nuxt的共享運(yùn)行時(shí)的上下文,此上下文在服務(wù)端和客戶端都存在。上下文(context)里包括: vue app的實(shí)例,運(yùn)行時(shí)的鉤子(hooks), 運(yùn)行時(shí)的配置變量和內(nèi)部狀態(tài),例如:ssrContext和payload。
const nuxtApp = useNuxtApp()
nuxtApp是一個(gè)運(yùn)行時(shí)的上下文, 你可以通過(guò)插件來(lái)擴(kuò)展它。使用provide方法就可以創(chuàng)建nuxt 插件,指定name,就可以在所有的組合式API和組件中通過(guò)name來(lái)調(diào)用value指定對(duì)象。
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
// Prints "Hello name!"
console.log(nuxtApp.$hello('name'))
例如,我們?cè)诓寮羞@么定義:
export default defineNuxtPlugin(async (nuxtApp) => {
return {
provide: {
auth: {
loggedIn,
session,
redirectTo,
updateSession,
},
},
};
})
則在客戶端代碼里,可以這么使用:
export const useAuth = () => useNuxtApp().$auth
userAuth().loggedIn()
這是擴(kuò)展Nuxt應(yīng)用的常用技法。
幾個(gè)注意點(diǎn)
記得clientOnly
使用client-only標(biāo)簽告訴nuxt這里不需要服務(wù)端渲染。
<template>
<client-only>
<vue-pdf-app style="height: 100vh;" :pdf="pdfUrl"></vue-pdf-app>
</client-only>
</template>
調(diào)試Nuxt3
vscode中l(wèi)aunch.json設(shè)置:
{
"name": "serve",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "yarn",
"runtimeArgs": ["serve"]
}
SSR部署
部署 Nuxt.js 服務(wù)端渲染的應(yīng)用不能直接使用 nuxt 命令,而應(yīng)該先進(jìn)行編譯構(gòu)建,然后再啟動(dòng) Nuxt 服務(wù),可通過(guò)以下兩個(gè)命令來(lái)完成:
nuxt build
nuxt start
通常腳本中會(huì)包裝成: yarn build or pnpm build 命令
構(gòu)建完后生成.output文件夾。該文件夾即是部署文件。.output文件夾下包含public和server兩個(gè)目錄。重命名為release后,再創(chuàng)建一個(gè)ecosystem.config.js文件。
ecosystem.config.js文件內(nèi)容:
module.exports = {
apps: [
{
name: 'CMSFront',
exec_mode: 'cluster',
instances: 'max',
script: './release/server/index.mjs',
env: {
NITRO_PORT: '9999',
}
}
]
}
然后再用pm2啟動(dòng)node進(jìn)程:
pm2 start ecosystem.config.js
當(dāng)然要先安裝一下pm2:
npm install pm2 -g
實(shí)例理解SSR
vue component定義如下:
<template>
<el-row :gutter="12" align="middle">
<el-col :span="4" v-for="book in bookList" style="margin-top:12px">
<el-card class="box-card" style="height:380px;background-color: antiquewhite;" shadow="hover">
<img :src="getCoverSrc(book.bookId)" class="image" />
<span style="padding-bottom: 20px;">
{{ book.bookName }}
</span>
<div class="card_footer">
價(jià)格:9.9元
</div>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const bookList:[] = ref()
const currentDate = ref(new Date())
function getCoverSrc(bookId) {
return `http://localhost:8888/cms/api/ebook/cover/${bookId}`
}
onMounted(async () => {
const { data } = await useFetch('/api/ebook/getall')
bookList.value = data.value
})
</script>
該組件顯示書(shū)籍列表。調(diào)用的server/api如下:
import { $fetch } from 'ofetch'
export default defineEventHandler(async (event) => {
const books = await $fetch('http://localhost:8888/cms/api/ebook/listall')
return books
})
該API從后端獲取數(shù)據(jù)。
如果不用SSR渲染,則返回給前端的頁(yè)面不包含書(shū)籍列表數(shù)據(jù),前端js會(huì)通過(guò)ajax請(qǐng)求去獲得書(shū)籍列表。采用SSR后,獲得書(shū)籍列表就在服務(wù)端完成了,返回給前端的就是渲染后的書(shū)籍列表html片段了。
通過(guò)curl命令請(qǐng)求一下page(每個(gè)page有自己的URL),就能驗(yàn)證。
打包方式
nuxi build
會(huì)為我們生成 .nuxt文件
部署
三種部署形式:
- SSR渲染部署。先nuxi build,再nuxi start
- 靜態(tài)部署。先nuxi generate編譯成靜態(tài)文件,會(huì)生成dist 文件夾,所有靜態(tài)化后的資源文件均在其中。然后扔到nginx上
- SPA部署。nuxi build --spa, 自動(dòng)生成dist/文件夾,然后扔到nginx上
UI框架
UI框架通常分為CSS框架和UI組件框架兩大類,前者有tailwindcss、windicss、unocss,后者有niave UI, element plus等。CSS框架比原生的css標(biāo)準(zhǔn)更高階,更容易記憶和書(shū)寫(xiě)。
和tailwindcss集成
tailwindcss目前成了很多前端框架的標(biāo)配,無(wú)論vue或React。tailwindcss采用約定成俗的樣式,能簡(jiǎn)化、語(yǔ)義化我們class的寫(xiě)法。它和bootstrap有點(diǎn)類似,和element-plus, antd是互補(bǔ),也有些重疊。
安裝:
yarn add -D @nuxtjs/tailwindcss
然后,nuxt.config.ts里:
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss']
})
再準(zhǔn)備一個(gè)~/assets/css/tailwind.css文件:
@tailwind base;
@tailwind components;
@tailwind utilities;
就可以開(kāi)箱即用了。
還有一個(gè)CSS框架unocss,大家也可以參考一下。
UI組件
使用Elment Plus
安裝:
yarn add element-plus
yarn add @element-plus/nuxt -D
配置:
export default defineNuxtConfig({
modules: [
'@element-plus/nuxt'
],
elementPlus: { /** Options */ }
})
則所有Element Plus 組件也都可以直接自動(dòng)導(dǎo)入,但圖標(biāo)例外,還需要手動(dòng)引入:
<script lang="ts" setup>
import { Document } from '@element-plus/icons-vue'
</script>
Naive UI
https://www.naiveui.com/zh-CN/os-theme文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-454440.html
老外的推薦:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-454440.html
- tailwind
- Element Plus: I was really hoping I would like this but I didn’t really enjoy it. Very stable, large community. 8/10
- NaiveUI: I really like this one, smaller community than some of the others. I am leaning toward using it though. 7/10
- Ant Design: Seems quite stable, not my style so I didn’t explore too far. 7/10
- Vuestic: Smaller than some of the others and missing some key components. Looks promising but I am not comfortable using it for a new production project today. 6/10
- Equal: Small and lightweight. organized well. 6/10
- Headless UI. Super small but it makes using tailwind a more viable option. 6/10
- PrimeVue: I have spent the most time with this one. It seems pretty good and is somewhat like Vuetify. There are several paid upgrades, which is fine with me, but something to note for others. 7/10
- daisyui, https://daisyui.com
- headlessui,https://headlessui.com
- Vuetify
參考文檔
- Nuxt3文檔
- GetStarted
- 目錄結(jié)構(gòu)
- 上手的例子
- Nuxt模塊集錦
- nuxt3-awesome-starter
- pinia-plugin-persistedstate和Nuxt3的集成
- nuxt3中的useNuxtApp使用詳解
- nextjs構(gòu)建策略
- UmiJs
相關(guān)工具鏈
- Vite、Rollup、Nuxt、Webpack、Vue Cli、Quasar、Esbuild
- 按需引入組件: unplugin-vue-components實(shí)現(xiàn)全自動(dòng)按需映入U(xiǎn)I組件庫(kù),unplugin-auto-import實(shí)現(xiàn)全自動(dòng)引入函數(shù)庫(kù)
到了這里,關(guān)于一文搞懂Nuxt3基本用法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!