项目初始化
项目创建
bash
代码解读
复制代码
npx nuxi init nuxt3-app npm install npm run dev
项目目录结构
bash
代码解读
复制代码
nuxt-app/ ├── .nuxt/ #无需关心 ├── .output/ #无需关心 ├── assets/ #静态资源 ├── components/ #公共组件-自动导入 ├── composables/ #公共函数/方法-自动导入 ├── content/ #Nuxt-Content相关-暂无需使用 ├── layouts/ #布局模板 ├── middleware/ #中间件-如路由导航守卫等 ├── node_modules/ #依赖项 ├── pages/ #相应页面-自动添加到路由 ├── plugins/ #插件目录 ├── public/ #静态资源-根目录 └── server/ #用于建立任何后端的逻辑如后端 API,这个目录下还包含了 `api`、`server` 和 `middleware` 来区分功能,不具有自动引入 ├── api/ ├── routes/ └── middleware/ ├── .gitignore ├── .nuxtignore ├── app.config.ts #客户端配置 ├── app.vue #入口组件 ├── nuxt.config.ts #项目配置 ├── package.json └── tsconfig.json #ts配置
路由
页面路由
nuxt3会自动整合vue-router,并且映射pages/
目录到应用的routes配置中,既按照pages中的目录文件自动创建相应的路由,如下所示。其中文件夹中index.vue为该路径默认路由组件,而组件/路由的嵌套则和经典的Vue-router类似,不过把router-view以及router-link分别用nuxtPage和nuxtLink进行了替代
js
代码解读
复制代码
//文件路径 pages -index.vue -detail --index.vue --some.vue //相应路由 /-index.vue /detail-/detail/index.vue /detail/some-/detail/some.vue
页面组件
html
代码解读
复制代码
<template> <div> <NuxtLink to="/test">test</NuxtLink> | <!-- 新增 --> <NuxtLink to="/">根</NuxtLink> <!--新增 --> <NuxtPage></NuxtPage> </div> </template>
动态路由
建立页面文件时,如果命名时将任何内容放在方括号内,它将被转换为路由参数。在文件名或目录中混合和匹配多个参数。
text
代码解读
复制代码
-| pages/ ---| index.vue ---| users-[group]/ -----| [id].vue
会生成路由:
ts
代码解读
复制代码
{ "routes": [ { "name": "users-group-id", "path": "/users-:group()/:id()", "component": "~/pages/users-[group]/[id].vue" }, ] }
根据上面的例子,你可以通过 $route 对象中的 params 访问组件中的 group & idx
html
代码解读
复制代码
<template> <ul class="text-base text-gray-600"> <li> 获取到的 group 是 <span class="text-green-500 text-xl">{{ group }}</span> </li> <li> 获取到的 id 是 <span class="text-green-500 text-xl">{{ id }}</span> </li> </ul> </template>
编程式导航
与 vue3.js 一样,在 setup 可以使用 useRouter、useRoute 来获取路由信息。
ts
代码解读
复制代码
<script setup> const route = useRoute(); const router = useRouter(); const { id } = route.params; console.log(router.getRoutes()); const handlerToHome = () => { router.push("/"); }; </script>
自定义路由
配置~/app/router.options.ts
在/src目录新增app文件夹,文件夹下建立router.options.ts文件(如果没有使用本文中的/src目录的配置,要在根目录下新增app文件夹)。该文件返回定制路由的函数来覆盖路由,如果返回 null 或 undefined, Nuxt将退回到默认路由。
text
代码解读
复制代码
-| pages/ ---| index.vue
ts
代码解读
复制代码
import type { RouterConfig } from "@nuxt/schema"; export default <RouterConfig>{ routes: (_routes) => [ { name: "home", path: "/home", component: () => import("~/pages/index.vue"), }, ], };
访问其他未定义页面路由,比如原本的 /
,提示 404
;原因是自定义路由完全替换了自动生成的路由。如果我们只是希望在自动导入的路由下,添加自定义路由,应该使用 pages:extend
钩子配置
配置nuxt.config.ts
使用 pages:extend 钩子配置扩展路由
ts
代码解读
复制代码
import { NuxtConfig } from "nuxt/config"; import { NuxtPage } from 'nuxt/schema'; export default defineNuxtConfig({ hooks: { 'pages:extend'(pages: NuxtPage[]) { pages.push({ name: 'error', path: '/error', file: '~/error.vue', // 可以传递 props 到业务组件内 props: { error: { statusCode: '500', statusMessage: '服务器开小差啦,请稍后重试', }, }, }); }, }, } as NuxtConfig);
布局Layout
默认布局
那些放在layouts/
目录下的SFC会被自动加载进来,如果我们创建的SFC名为default.vue
,将会被用于项目所有页面中作为布局模板。
layouts/default.vue:
xml
代码解读
复制代码
app.vue <template> <div> hello,nuxt3! <NuxtPage/> <slot /> </div> </template> layout/default.vue <template> <div> 通用布局页,default.vue: <slot /> </div> </template> pages/hello.vue <template> <div> hello page </div> </template>
默认布局会自动嵌套在app.vue中
自定义布局
如果我们的布局文件名不叫default,而是别的,比如custom.vue
,想要使用它们,就必须在某个页面中设置页面属性layout
。
custom.vue:
xml
代码解读
复制代码
<template> <div> 内容来自自定义布局页custom.vue! <slot /> </div> </template>
可以在helloworld.vue中试试custom这个布局,helloworld.vue:
xml
代码解读
复制代码
<script> export default { layout: "custom" } </script>
NuxtLayout
可以使用NuxtLayout组件结合slots获得完全控制力,同时需要设置组件选项layout: false
。
helloworld.vue
xml
代码解读
复制代码
<template> <NuxtLayout name="custom"> <template #header> <h1>hello page</h1> </template> some content... </NuxtLayout> </template> <script> export default { layout: false, }; </script>
修改一下custom.vue
xml
代码解读
复制代码
<template> <div> 内容来自自定义布局页custom.vue! <slot name="header"/> <slot /> </div> </template>
我们甚至能组合多个布局页:
xml
代码解读
复制代码
<template> <div> <NuxtLayout name="custom"> <template #header> <h1>hello page</h1> </template> some content... </NuxtLayout> <NuxtLayout name="default"> some content... </NuxtLayout> </div> </template>
由于需要设置layout选项,所以在这个script标签旁边同时使用
xml
代码解读
复制代码
<script> export default { layout: "custom", }; </script> <script setup> // your setup script </script>
组件components
自动导入组件
我们把Vue组件放在components/
目录,这些组件可以被用在页面和其他组件中,以往我们使用这些组件需要导入并注册它们,但Nuxt会自动导入components/
目录中的任意组件。比如:
diff
代码解读
复制代码
| components/ --| TheHeader.vue --| TheFooter.vue
layouts/default.vue:
xml
代码解读
复制代码
<template> <div> <TheHeader /> <slot /> <TheFooter /> </div> </template>
组件名称约定
没有嵌套的组件会以文件名直接导入,但如果存在嵌套关系哪?例如下面的路径:
diff
代码解读
复制代码
| components/ --| base/ ----| foo/ ------| Button.vue
那么组件名称将会基于路径和文件名连起来,比如上面的base/foo/Button.vue
注册名称将会是BaseFooButton
,将来用起来会像下面这样:
xml
代码解读
复制代码
<BaseFooButton />
组件懒加载
如果在组件名前面加上Lazy前缀,则可以按需懒加载该组件,可用于优化打包尺寸。
比如,下面的用法:
xml
代码解读
复制代码
<template> <div> <h1>Mountains</h1> <LazyMountainsList v-if="show" /> <button v-if="!show" @click="show = true">显示列表</button> </div> </template> <script setup> import {ref} from 'vue' const show = ref(false) </script>
数据获取
nuxt3
中提供的数据获取函数有以下四个:
- useFetch
- useLazyFetch
- useAsyncData
- useLazyAsyncData
注意:它们都必须在
setup
或生命周期钩子
中使用
useAsyncData
在页面、组件或插件中都可以使用useAsyncData
获取那些异步数据。比如:
typescript
代码解读
复制代码
const { data: Ref<DataT>, // 返回的数据 pending: Ref<boolean>, // 加载状态指示器 refresh: (force?: boolean) => Promise<void>, // 强制刷新函数 error?: any // 请求失败的错误信息 } = useAsyncData( key: string,// 唯一键用于多次请求结果去重 fn: () => Object,// 返回数值的异步函数 // lazy - 是否在路由之后才请求数据,server - 是否在服务端请求数据 options?: { lazy: boolean, server: boolean } )
获取待办事项数据,index.vue:
xml
代码解读
复制代码
<template> <div> <!-- 待办列表 --> <div v-for="todo in todos" :key="todo.id"> <input type="checkbox" v-model="todo.completed"> <strong>{{todo.title}}</strong> </div> </div> </template> <script setup lang="ts"> const { data: todos } = await useAsyncData( 'count', () => $fetch('/api/todos')) </script>
$fetch使用参考ohmyfetch
useLazyAsyncData
该方法等效于useAsyncData
,仅仅设置了lazy
选项为true,也就是它不会阻塞路由导航,这意味着我们需要处理data为null的情况
(或者给data设置一个默认值)
useFetch
页面、组件或者插件中可以使用useFetch
获取任意URL资源。
useFetch
是对useAsyncData
包装,自动生成key同时推断响应类型,用起来更简单。
看下面方法签名,基本完全相同:
typescript
代码解读
复制代码
const { data: Ref<DataT>, pending: Ref<boolean>, refresh: (force?: boolean) => Promise<void>, error?: any } = useFetch(url: string, options?)
useLazyFetch
该方法等效于useFetch
,仅设置了lazy
选项为true,所以它不会阻塞路由导航,这意味着我们需要处理data为null的情况(或者通过default选购给data设置一个默认值)
最佳实践
只选取需要的数据
由于请求回来的数据会存储在页面payload中,甚至包括那些没有用到的字段,所以文档中明确建议大家只选择那些组件用到的数据,我们可以设置$fetch的pick
选项。
比如,下面的用法:
ts
代码解读
复制代码
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
状态共享
Nuxt3提供了 useState
创建响应式且服务端友好的跨组件状态共享能力。
useState
是服务端友好的 ref替换。它的值在服务端渲染(客户端注水的过程中)将被保留并通过唯一key在组件间共享。
方法签名
js
代码解读
复制代码
useState<T>(key: string, init?: () => T): Ref<T>
- key:唯一键用于去重
- init:提供初始值的函数
useState实践
声明一个状态,index.vue
javascript
代码解读
复制代码
const counter = useState("counter", () => Math.round(Math.random() * 1000))
html
代码解读
复制代码
<button @click="counter++">+</button> {{ counter }} <button @click="counter--">-</button>
共享状态
我们的全局状态当然想要在组件之间共享,此时可以利用nuxt的composables自动导入特性,把它们定义在composables目录中,这样他们将成为全局类型安全的状态。
composables/useCounter.ts
javascript
代码解读
复制代码
export const useCounter = () => useState("counter", () => Math.round(Math.random() * 1000));
配置
nuxt3项目中的相关配置主要放在项目根目录下的nuxt.config.ts
文件中,除了nuxt3框架需要的配置属性外,你也可以扩展添加自已处理的配置。 默认情况下,配置文件是这样的。
arduino
代码解读
复制代码
export default defineNuxtConfig({ // My Nuxt config })
运行时配置
runtimeConfig
接口的作用与环境变量类似,在项目中可以使用此接口暴露出来的变量。默认情况下,这些变量只能在服务端使用,但在runtimeConfig.public
下的配置也可以在客户端中使用。 这些变量的值需要定义在nuxt.config
中,并且可以被环境变量覆盖。 例如:
arduino
代码解读
复制代码
export default defineNuxtConfig({ runtimeConfig: { // The private keys which are only available server-side apiSecret: '123', // Keys within public are also exposed client-side public: { apiBase: '/api' } } })
环境变量覆盖
ini
代码解读
复制代码
# This will override the value of apiSecret NUXT_API_SECRET=api_secret_token
应用配置
app.config.ts
文件默认在项目的根目录下,里头的配置主要是在项目的构建和编译阶段
中会使用到。与运行时的配置相反,这里的配置不能被环境变量覆盖。
配置文件中最简单的内容就是导出一个defineAppConfig
方法,此方法中有一个你配置对象。
php
代码解读
复制代码
// app.config.ts export default defineAppConfig({ title: 'Hello Nuxt', theme: { dark: true, colors: { primary: '#ff0000' } } })
这些变量可以在项目中通过useAppConfig
接口来使用.
xml
代码解读
复制代码
<script setup> const appConfig = useAppConfig() </script>
runtimeConfig
与app.config
这两个的作用都是暴露变量给项目中使用。那在实际项目开发过程中到底使用哪个呢。 runtimeConfig:
项目中需要使用指定的私有和仅有的tokes时 app.config:
可以放一些需要在构建时使用的公共Token, 例如主题变量,标题等不敏感的数据
env配置
.env的变量会打入到process.env中,符合规则的会覆盖runtimeConfig的变量 默认加载 .env
文件的配置
.env
文件内容
ini
代码解读
复制代码
NUXT_PUBLIC_API_BASE=http://127.0.0.1:53105/mk
.env.production
文件内容
bash
代码解读
复制代码
NUXT_PUBLIC_API_BASE=https://vaebe.top:53104/mk
也许你发现了 NUXT_PUBLIC_API_BASE
这个变量, 看下上边的 设置环境变量 就会发现其实就是把对象字段 key
进行拼接。
所以 API_BASE
对应的就是 runtimeConfig\public
配置里的 apiBase
这个字段,.env
文件的变量应该与 runtimeConfig
配置的内容相同。
如果不在 public
下那就应该是 NUXT_API_BASE=xxxxxxx
渲染模式
Nuxt支持不同的渲染模式
- 通用渲染
- 客户端渲染
- 混合渲染
- CDN边缘服务器上渲染
通用渲染
通用渲染模式是服务器端+客户端一起使用的。服务器会首先向浏览器返回一个完整的HTML,这个HTML可能是构建的时候预渲染,也有可能是执行的时候再渲染出来的。这就是服务端渲染的步骤
然后为了不失去客户端渲染的优势,例如动态界面和页面过度,客户端再下载这个HTML之后,还会在后台加载原本在Server上运行的js代码。浏览器会再次解释调用它
这回使得这个静态页面,重新拥有了动态能力。这个过程叫做Hydration,水合作用
服务端渲染的好处
- 性能:用户一下子就能获得完整的内容,响应更加快了
- SEO: 搜索引擎一般只能爬取HTML,不会执行js。所以你的内容更加容易呈现给搜索引擎
服务端渲染的缺点
- 开发麻烦,需要考虑不同环境下的api
- 成本,服务器的成本增加了
客户端渲染
就像是传统的Vue项目一样。打包出来的html会执行main.js。这包含完整的Vue.js代码,然后开始解析用户的操作
好处:
- 开发速度,更快了,而且只有一套浏览器的api
- 便宜,运行成本转嫁给用户了
- 离线,它允许用户下载完完整的代码后,离线运行一时间
缺点
- 性能,用户要等待下载、解析和运行js文件
- SEO不好,同上
Nuxt也提供这种方式
ts
代码解读
复制代码
export default defineNuxtConfig({ ssr: false })
部署一个静态的客户端渲染的应用程序
如果你使用 nuxi generate
或 nuxi build --prerender
命令将应用部署到静态托管服务上,那么默认情况下,Nuxt 会将每个页面渲染为单独的静态 HTML 文件。
如果你只使用客户端渲染,这可能是没有必要的。你只需一个index.html
文件,再加上 200.html
和 404.html
的回退页面,你可以告诉你的静态网页托管服务对所有请求提供这些文件。 为了实现这一点,我们可以改变路由的预渲染方式。只需在你的 nuxt.config.ts
文件中的 hooks 添加以下内容:
ts
代码解读
复制代码
export default defineNuxtConfig({ hooks: { 'prerender:routes' ({ routes }) { routes.clear() // Do not generate any routes (except the defaults) } }, })
然后,Nuxt只生成三个文件
index.html
200.html
404.html
混合渲染
如果你想有的部分使用通用渲染,有部分使用客户端渲染。也是可以的
例如一个CMS管理系统。一些向外展示的页面应该是静态的,但是管理后台需要注册功能,更像一个动态应用。
Nuxt支持在路由规则中设置渲染模式,以支持混合渲染。
ts
代码解读
复制代码
export default defineNuxtConfig({ routeRules: { // 构建的时候就进行预渲染 '/': { prerender: true }, // 产品页面按需生成,在后台重新验证,缓存直到API响应更改 '/products': { swr: true }, // 产品页面按需生成,后台重新验证,缓存1小时(3600秒) '/products/**': { swr: 3600 }, // 博客文章页面按需生成,在后台重新验证,在CDN上缓存1小时(3600秒) '/blog': { isr: 3600 }, // 博客文章页面按需生成一次,直到下一次部署,缓存在CDN上 '/blog/**': { isr: true }, // 管理仪表板只在客户端渲染 '/admin/**': { ssr: false }, // 在API路由中添加cors头 '/api/**': { cors: true }, // 重定向遗留url '/old-page': { redirect: '/new-page' } } })
路由规则
你可以使用的不同属性如下:
redirect
: string — 定义服务器端重定向。ssr
: boolean — 对应用程序的部分区域禁用服务器端渲染,并将其设置为仅单页应用(SPA)模式,通过设置ssr: false
实现。cors
: boolean — 自动添加跨源资源共享(CORS)头部,通过设置cors: true
实现。可以通过覆盖headers
属性来定制输出。headers
: 对象 — 向网站的特定部分添加特定的头部信息——例如,你的资源文件。swr
: 数字或布尔值 — 向服务器响应添加缓存头部,并在服务器或反向代理上为可配置的存活时间(TTL)缓存该响应。Nitro 的 Node 服务器预设能够缓存完整的响应。当 TTL 到期后,会发送已缓存的响应,同时在后台重新生成页面。如果使用true
,则会添加一个无过期时间的验证缓存(stale-while-revalidate)头部,但不指定最大有效期(MaxAge)。isr
: 数字或布尔值 — 其行为与swr
相同,只是我们能够在支持此功能的平台(目前为 Netlify 或 Vercel)上将响应添加到内容分发网络(CDN)缓存中。如果使用true
,则内容会在 CDN 中保留到下一次部署。prerender
: 布尔值 — 在构建时预先渲染路由,并将它们作为静态资源包含在构建中。experimentalNoScripts
: 布尔值 — 对网站的部分区域禁用 Nuxt 脚本和 JavaScript 资源提示的渲染。appMiddleware
: 字符串、字符串数组或记录类型(键为字符串,值为布尔值)——允许你定义应该或不应该在 Vue 应用程序部分(即,非 Nitro 路由)的页面路径中运行的中间件。
边缘渲染
这是一个Nuxt引入的强大的功能,它使得Nuxt像静态网站一样支持CDN分发。
边缘侧渲染的工作原理是将渲染过程推送到网络的“边缘”位置,即 CDN 的边缘服务器。需要注意的是,ESR 更多是一种部署目标而非实际的渲染模式。
当请求某个页面时,请求不会直接到达原始服务器,而是被最近的边缘服务器截获。这个服务器会生成页面的 HTML 并将其发送回用户。这一过程减少了数据传输的实际距离,降低了延迟,并加快了页面加载速度
边缘侧渲染得以实现要归功于 Nitro,这是为 Nuxt 3 提供动力的服务器引擎。Nitro 支持跨平台,包括 Node.js、Deno、Cloudflare Workers 等。
目前可以利用边缘侧渲染的平台有:
- 使用 Git 集成和
nuxt build
命令,在 Cloudflare Pages 上无需任何配置即可实现边缘侧渲染。 - 使用
nuxt build
命令和环境变量NITRO_PRESET=vercel-edge
在 Vercel 边缘函数上启用边缘侧渲染。 - 使用
nuxt build
命令和环境变量NITRO_PRESET=netlify-edge
在 Netlify 边缘函数上启用边缘侧渲染。
值得注意的是,当你使用边缘侧渲染结合路由规则时,可以使用混合渲染(Hybrid Rendering)。
原文链接:https://juejin.cn/post/7419894731813912586