了解

Nuxt3 是基于 Vue3 的一个框架,它提供了一些开箱即用的功能,如路由、状态管理、服务器端渲染等,可以帮助开发者快速构建高性能的 Vue 应用。

为什么要学习?工作需要,SSR 顺便了解下,写篇博客记录一下

优点:

  • 支持 Vue3 & Vite
  • 更好的 SEO 搜索引擎优化
  • 提高开发效率(官网这么说,上手试试)
    • 自动化路由
  • 支持多种部署方式
    • 服务端渲染
    • 静态化生成

SPA 单页应用

single page application 单页应用,一般也称为客户端渲染 CSR client side Render。整个站点由一个 HTML 页面构成, 所有内容通过 JS 动态加载和渲染。一般的,用户在访问 SPA 站点,页面只有第一次加载会请求资源,后续都是通过 AJAX 异步请求获取数据并动态更新页面。

存在问题:

  • 首屏加载慢(白屏问题),加载大量 JS 和 CSS 文件
  • SEO 不友好(相对)搜索引擎无法直接抓取页面内容
  • 内存占用高,会在内存中缓存大量数据

SSR 服务端渲染

将客户端渲染和服务端渲染结合起来,主要是在服务端生成好 HTML (结构和内容)代码,并将其发送到浏览器端

优点:

  • 性能相对好,首屏更快
  • SEO 友好
  • 用户体验佳
  • 内存占用相对低

缺点:

  • 开发复杂度增加
  • 服务器负载增加
  • 需要更多的服务器资源

SSG 静态化生成

静态化生成 Static Site Generation,在构建时生成静态 HTML 文件,并将这些文件部署到静态文件服务器上。用户访问时,直接返回生成的静态文件,无需服务端参与。

优点:

  • 性能最好,首屏最快
  • SEO 友好
  • 用户体验最好
  • 内存占用最低
  • 部署简单

缺点:

  • 内容更新需要重新构建
  • 频繁改动的场景不适合

Nuxt 基础

https://nuxt.com/docs/getting-started/upgrade

Nuxt3

  • nuxt3 默认使用 vite 构建,也支持 webpack
  • 内置官方自研服务器引擎 Nitro
  • 全面支持 ES6 模块化
  • 更好地支持 Vue3 Composition API 也兼容 Vue2 Options API
  • TS 支持更完善

开发环境

  • nodejs 18+
  • npm 8+
  • pnpm 8+
  • vue 3.0+
  • vite 3.0+
  • typescript 4.0+

npx nuxi@latest init learn-nuxt

pnpm install

pnpm run dev

欢迎页

UI 库

  • element antd 这些随便选,按官网来就好

pnpm add ant-design-vue@4

配置按需加载

pnpm add unplugin-vue-components -D

这里就直接把 nuxt.config.ts 配置帖上来 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
// devtools: { enabled: true },
vite: {
vue: {
customElement: true,
},
// 配置按需加载
plugins: [
Components({
resolvers: [
AntDesignVueResolver({
// css in js
importStyle: 'less',
}),
],
}),
],
// 配置css
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
lessOptions: {
modifyVars: {
'primary-color': '#ea6f5a',
'link-color': '#1DA57A',
'border-radius-base': '2px',
},
},
},
},
},
// 配置SSR
ssr: {
// 在服务端渲染时,将框架作为外部模块处理,不打包进输出文件
noExternal: ['ant-design-vue'],
},
},
})

页面

在 Nuxt3 中,页面是指应用程序中的路由页面,这些页面可以被搜索引擎索引并直接通过 url 访问。页面由 Vue 组件构成,并且可以在 pages 目录下创建

app.vue 是最顶级入口,要这样用需改动一下,添加 NuxtPage Nuxt 的内置组件

组件

我们可以在根目录下创建 components 目录来存放组件,不需导入和注册即可直接使用

路由

Nuxt3 路由基于 vue-router 并通过文件目录结构来自动生成

而动态路由,可以用 [dynamic] 定义动态路由,如果是可选参数可以用 [[dynamic]]

动态路由及参数获取

动态路由及参数获取

如图创建目录 users-[group] 及文件 [id].vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h1>group - {{ group }}, id - {{ id }}</h1>
<a-button type="primary">click me</a-button>

<HelloWorld />
</div>
</template>

<script setup lang="ts">
const { params } = useRoute()

const { group, id } = params
</script>

通过useRoute获取参数

嵌套路由的使用

在 pages 目录下创建一个 parent.vue 文件。如果我们想使用子路由的形式, http://localhost:3000/parent/child

需要继续创建 pages/parent 目录,然后添加对应文件即可,如 pages/parent/child.vue。 页面访问照旧,现在 http://localhost:3000/parent/child 就可以访问到子路由了

嵌套路由的使用

路由跳转

用内置的 NuxtLink 即可 (基本同 router-link)

用自定义事件跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<button @click="routeTo">自定义跳转</button>
<NuxtLink :to="{path: 'about', query: userinfo: JSON.stringify(userinfo)}">自定义跳转</NuxtLink>
</template>
<script setup lang="ts">
const router = useRouter()
const userInfo = ref({
id: 1,
username: '张三',
})
const routeTo = () => {
router.push({
path: '/about',
query: {
userinfo: JSON.stringify(userInfo.value),
},
})
}
</script>

路由中间件

Nuxt3 提供了一个路由中间件,允许在路由跳转前后执行特定代码,如页面访问权限验证,记录用户的访问记录等(路由拦截、路由守卫) 我们可以在项目根目录下创建 middleware 目录来存放中间件

  • 命名路由中间件

防止在 middleware 中,在页面上使用会通过异步导入自动加载

  • 匿名(或内联)路由中间件

直接在使用的页面中定义

  • 全局路由中间件

放在 middleware 中(带有 .global 后缀)并将在每次路由更改时自动运行

  • 用法(命名路由中间件)

路由中间件是一个导航守卫,它接受当前路由和下一个路由作为参数

1
2
3
4
5
6
7
8
9
10
export default defineNuxtRouteMiddle((to, from) => {
// 模拟判断用户是否登录
// 这个状态可存到pinia 或者 sessionStorage中
let isLogin = false

// 未登录跳转到登录页
if (!isLogin) {
return navigateTo('/login')
}
})

about.vue

1
2
3
4
5
<script setup>
definePageMeta({
middleware: 'auth',
})
</script>

通过 Nuxt 内置函数,definePageMeta,传入我们要加入的中间件,现在我们访问 /about (由于未登录),会自动跳转到 /login

如果页面很多,有的只需要做下简单判断,没必要都写 middleware 里,这时就可以用匿名路由中间件,同上,只是写到组件里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- about.vue -->
<script setup>
definePageMeta({
middleware: defineNuxtRouteMiddleware(() => {
// 模拟判断用户是否登录
let isLogin = JSON.parse(sessionStorage.getItem('isLogin'))

// 未登录跳转到登录页
if (!isLogin) {
return navigateTo('/login')
}
}),
})
</script>
  • 全局路由中级件

middleware > run.global.ts 只要定义了该文件,应用内路由跳转时就会自动执行

1
2
3
4
5
6
7
export default defineNuxtRouteMiddleware((to, from) => {
if (to.path === '/') {
console.log(`欢迎访问 Nuxt demo`)
} else {
console.log(`路由跳转`, to.path)
}
})

Nuxt 插件

Nuxt3 中,插件是一种在构建时注册和使用的模块,可以用于处理常规的 JS 一来和特殊的框架配置,同时也可以与 Nuxt 项目一起发布

只有在 plugins 目录的顶层文件(或任何子目录的索引文件)才会被注册为插件,例如:

  • plugins/myPlugins.ts 注册
  • plugins/Aplugins/index.ts 注册
  • plugins/Bplugins/file.ts 不会被注册
1
2
3
4
5
6
7
export default defineNuxtPlugin(() => {
return {
provide: {
welcome: (name = 'fridolph') => `你好,欢迎访问 ${name} 的 Nuxt demo`,
},
}
})

用法 1:作为函数在页面中使用

1
2
3
4
5
6
7
8
<template>
<Header> </Header>
<p>{{ $welcome() }}</p>
</template>

<script setup>
const { $welcome } = useNuxtApp()
</script>

用法 2:挂载到原型上(类似于 vue3 app.config.globalProperties 挂载全局方法)

1
2
3
4
5
6
7
8
9
10
11
// 由于 message modal 这类组件通常用实例生成,我们可以用该形式注册组件
import { message } from 'ant-design-vue'
import 'ant-design-vue/es/message/style'

export default defineNuxtPlugin(() => {
return {
provide: {
message,
},
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<a-button @click="handleMsg">消息</a-button>
</div>
</template>

<script setup lang="ts">
const { $message } = useNuxtApp()

const handleLogin = () => {
$message.info('Hello, Nuxt app!')
}
</script>

Nuxt 模块设计

Nuxt3 的模块系统是一个插件化的架构,它允许开发者使用 Nuxt3 构建应用时,以模块的形式来封装和共享各种功能。

Nuxt3 模块系统的设计目录是让开发者能够通过安装和配置模块,快速增加应用程序的功能,而不需要写发咋的代码或进行深度的集成。

  • @nuxt/content
  • @nuxtjs/auth
  • @nuxtjs/i18n
  • @nuxtjs/tailwindcss
  • @pinia/nuxt
1
2
3
4
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
})

在根目录下创建 store 目录即可,和 pinia 正常用法没啥区别,这里就不贴代码了

这里额外提一下 pinia 持久化,因为刷新页面,数据会初始化

pnpm add pinia-plugin-persistedstate@/nuxt

1
2
3
4
5
6
7
8
9
10
11
import { persistedState } from '#imports'

export const useMyStore = defineStore('myStore', {
state: () => ({
// ...
}),
persist: {
// storage: persistedState.sessionStorage,
storage: persistedState.localStorage,
},
})

useState

在 Vue 生态中,我们通常使用 vuex 或是 pinia 来实现状态管理,而在 Nuxt3 中,官方提供了更简洁的 useState 方法,SSR 友好,通过 useState 存储的状态能在 server 和 client 之间共享。

  • 使用

需在目录下创建 composables 目录,并在该目录下创建文件,例如:composables/state.ts

useCookie

useCookie 是 Nuxt3 框架中的一个组合 API,用于在客户端和服务端都能访问情况下,使用 useCookit 保存和读取数据(基于开源 cookie 包封装)

使用场景:

  • 存储用户信息
  • 存储用户偏好,例如语言、主题等
  • 存储购物车信息

获取数据

Nuxt3 中的 API 数据获取:

  • useFetch

基于 useAsyncData 和 $fetch 封装的。useFetch 将 lazy 参数设置为 true 时,就能实现 useLazyFetch 的效果

  • useLazyFetch

  • useAsyncData

  • useLazyAsyncData

  • $fetch 全局辅助函数

useFetch 可以实现其他几个 API 的全部功能,实际开发使用 useFetch 即可,其他具体情况具体分析。

在组件中使用$fetch而不使用useAsyncData进行包装会导致数据被获取两次:首先在服务器端获取,然后在客户端进行混合渲染期间再次获取,因为$fetch 不会将状态从服务器传递到客户端。因此,获取将在两端执行,因为客户端需要再次获取数据。
我们建议在获取组件数据时使用 useFetch 或 useAsyncData + $fetch 来防止重复获取数据。