这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天
本文章只介绍基本的使用,一些 API 的复杂使用不在此笔记中
1. 两种渲染模式
1.1 客户端渲染
优点:速度快、用户体验好
缺点:
- 最开始拿到的一个空的
HTML文件和JS文件,首屏渲染慢。 - 最开始拿到的文件没有实质内容,对于
SEO(搜索引擎)不友好
1.2 同构渲染(服务端渲染)

SSR 会消耗服务器性能,所以一些后台管理系统不需要使用 SSR 构建
2. Nuxt Configuration
2.1 nuxt.config.ts
nuxt.config.ts 文件是 Nuxt 项目的配置入口文件,可以
控制或扩展应用程序。
最开始的代码只是包含导出 defineNuxtConfig 函数,在函数内可以自定义自己的配置,defineNuxtConfig 函数是在全局生效的,无需 import
// nuxt.config.ts
export default defineNuxtConfig({
// My Nuxt Config
})
这个文件经常会在文档中被提及,比如添加一些脚本、注册模块或者改变渲染模式。
2.2 环境变量和 token
runtimeConfig API 可以向外暴露一些数据,像是环境变量在你的其他应用中。默认值只能在服务端访问,如果需要客户端也能访问,则需要 runtimeConfig.public API
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// only server-side
apiSecret: '123',
// client-side and server-side
public: {
apiBae: '/api'
}
}
})
当然,也可以在跟 nuxt.config.ts 同目录下新建 .env 文件
# 这个会覆盖 nuxt.config.ts 配置的 apiSecret
NUXT_API_SECRET=555
当这些变量配置好后,就可以在其他组件中使用了,使用方式:useRuntimeConfig
客户端只能访问 apiBase,useFetch 只是测试客户端是否能同时访问 apiSecret 和 apiBase
// pages/index.vue
<script setup lang="ts">
const runtime = useRuntimeConfig()
// 客户端只能访问 public 中的属性
const apiBase = runtimeConfig.public.apiBase
// useFetch('/api/count') // 只是为了访问一下
</script>
<template>
<div>apiBase:{{apiBase}} </div>
</template>
服务端可以同时访问 apiSecret 和 apiBase
// server/api/count.ts
let count = 0
export default () => {
count++
const runtimeConfig = useRuntimeConfig()
console.log('apiSecret', runtimeConfig.apiSecret)
console.log('apiBase', runtimeConfig.public.apiBase)
return JSON.stringify(count)
}
2.3 App Configuration
app.config.ts 文件也是在项目根目录,不过它暴露出的都是公共变量并且在构建的时候就已经确立。对比 runtimeConfig,这些变量不能被环境变量覆盖。
defineAppConfig 包含一个对象,且是全局注册,无需import,示例:
// app.config.ts
export default defineAppConfig({
title: 'Hello Nuxt',
theme: {
dark: true,
colors: {
highlight: '#ff0000'
}
}
})
使用方法:组件中使用 useAppConfig API
// pages/appconfig.vue
<script setup lang="ts">
const appConfig = useAppConfig()
const { title, theme } = appConfig
</script>
<template>
<div>
<div>title: {{ title }}</div>
<div>theme: {{ theme.dark ? 'dark' : 'light' }}</div>
<div :style="`color: ${theme.colors.highlight}`">Test Text</div>
</div>
</template>
2.4 runtimeConfig 和 app.config
runtimeConfig 和 app.config 都可以用来暴露变量给我们的应用程序,但是它们的应用场所不同
runtimeConfig:在构建之后需要被使用的私密的或者公开的 tokenapp.config:在构建时确定的公共 token、网站配置(如主题变量、标题和任何不敏感的项目配置)。
以下是官网的一些解释,我翻译不出来()
| Feature | runtimeConfig | app.config |
|---|---|---|
| Client Side | Hydrated | Bundled |
| Environment Variables | ✅ Yes | ❌ No |
| Reactive | ✅ Yes | ✅ Yes |
| Types support | ✅ Partial | ✅ Yes |
| Configuration per Request | ❌ No | ✅ Yes |
| Hot Module Replacement | ❌ No | ✅ Yes |
| Non primitive JS types | ❌ No | ✅ Yes |
同时还有其他构建工具的配置信息,参考:Configuration · Get Started with Nuxt
3. Views
Nuxt 提供了几个组件层来实现应用程序的用户界面
3.1 app.vue
Nuxt 默认会把 app.vue 当做是入口文件,并且渲染每个路由的内容
<template>
<div>
<h1>Welcome to the homepage</h1>
</div>
</template>
3.2 Components
大多数的组件都是可循环利用的,比如按钮和菜单组件,在 Nuxt 中,在根目录的 components 文件夹创建组件,这样的组件是全局的,意味着在其他组件使用无需导入
// components/AppAlert.vue
<template>
<div>
<div>这是 AppAlert 组件 </div>
<slot />
</div>
</template>
// app.vue
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
这个组件是自动导入的,无需 import
</AppAlert>
</div>
</template>
3.3 pages
pages 下面的文件采用了一种特殊的路由模式,每一个文件都代表着不同的路由信息。
为了使用 pages,创建 pages/index.vue 文件并且在 app.vue 添加 <NuxtPage /> 标签(和 view-router 类似,不必一定只在 app.vue 中添加)
// app.vue
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
这个组件是自动导入的,无需 import
</AppAlert>
<NuxtPage />
</div>
</template>
pages/index.vue 表示的路由地址为 ‘/’,以 index 文件名前缀开头的,默认为跟路由,例如 pages/users/index.vue 代表的路由地址为 /user/
<template>
<div>
pages 子路由
</div>
</template>
3.4 Layouts
Layouts 是包含多个页面的通用用户界面(如页眉和页脚显示)的页面的包装器。布局是Vue文件,使用
<slot /> 组件去展示 page 页的内容。layouts/default.vue 文件会被认为是默认入口。
// layouts/default.vue
<template>
<div>
<AppHeader />
<slot />
<AppFooter />
</div>
</template>
这时去掉 app.vue 便会展示以上内容
4. Assets
Nuxt 使用两个文件夹去处理 stylesheets、fonts 或者 images
public内的文件会原封不动保留在根目录assets内的文件会被编译工具进一步处理
4.1 public
public/ 目录提供静态资源并且这些资源可以通过浏览器地址访问到。
访问 public/ 目录下的内容只需要使用 ‘/’ 即可
示例:
// pages/public.vue
<template>
<img src="/4.png" alt="">
</template>
同时访问 http://localhost:3000/4.png 地址,也可以访问到图片
4.2 assets
Nuxt 使用 Vite 或者 webpack 为打包工具,它们主要的功能就是生产优化 JS 文件,但是它们可以通过创建扩展(Vite)或者装在(webpack)去生产另一些静态资源,像是 stylesheets、fonts 或者 SVG。此步骤转换原始文件主要用于性能或缓存目的(例如样式表缩小或浏览器缓存无效)。
可以使用 ~/assets/ 来访问 assets/ 目录下的文件。
// pages/assets.vue
<template>
<img src="~/assets/2.png" alt="">
</template>
4.3 Global Styles Imports
为了全局引用 Nuxt 组件样式,可以在 nuxt.config.ts 中使用 Vite 的选项
示例:
// assets/_colors.sass
$primary: #49240F
$secondary: #E4A79D
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
sass: {
additionalata: `@use "@/assets/_colors.sass" as *\n`
}
}
}
}
})
5. Routing
Nuxt 采用文件系统路由,在这里不再细讲
5.1 Navigation
<NuxtLink> 组件(双标签),用来指定 Nuxt 中路由的跳转,它最终会被渲染为 a 标签,但是它的跳转不会经由服务器。
// components/Navigation.vue
<template>
<ul>
<li><NuxtLink href="/">Home</NuxtLink></li>
<li><NuxtLink href="/appconfig">appconfig</NuxtLink></li>
<li><NuxtLink href="/assets">assets</NuxtLink></li>
<li><NuxtLink href="/pages">pages</NuxtLink></li>
<li><NuxtLink href="/public">public</NuxtLink></li>
</ul>
</template>
5.2 Route Parameters
Nuxt 以 [] 这种形式创建动态路由,同时使用 useRoute 方法获取动态路由的参数
// /pages/[id].vue
<script setup lang="ts">
const route = useRoute()
const id = route.params.id
</script>
<template>
id: {{ id }}
</template>
路由跳转
<template>
<ul>
<li><input type="text" v-model="id" /></li>
<li><NuxtLink :href="`/${route}`">id</NuxtLink></li>
</ul>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { RouteLocationRaw } from 'vue-router';
const id = ref(0)
const route = <RouteLocationRaw>(id)
</script>
5.3 Route Middleware
Nuxt 提供了可定制的路由中间件,你可以编写合适的代码,在路由跳转之前做一些工作。
注意: 这里说的路由中间件跟后端的中间件完全不同
三种不同的路由中间件:
- 匿名路由中间件:直接在
pages文件夹对应的路由使用 - 命名路由中间件:放置在
middleware/文件夹中,会同过一部导入自动加载到 page 中(注意: 这种路由中间件命名方式一般为 kebab-case,例如someMiddleware应当命名为some-middleware) - 全局路由中间件:也是放置在
middleware/文件夹(带有.global后缀),并且在路由跳转时会自动执行
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
// to 代表去了哪里,from 代表去哪里来
console.log('to: ', to)
console.log('from: ', from)
})
// pages/middleware.vue
<template>
<button @click="router.back()">回退</button>
</template>
<script setup lang="ts">
const router = useRouter()
definePageMeta({
middleware: 'auth' // 与文件名字相同
})
</script>
6. SEO and Meta
通过 head config、composables 和 components 改善产品的 SEO
6.1 App Head
在 nuxt.config.ts 中提供了 app.head 属性,允许你定制化整个项目的 head 头部标签
这种方式不允许你使用响应式的数据,如果想用响应式数据全局设置 head 头部,可以在 app.vue 中使用 useHead
在盒子内容之外,Nuxt3 设置了合适的默认头部信息,这些可以自己手动设置覆盖
charset:utf-8viewport:width=device-width, initial-scale=1
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-16',
viewport: 'width=500, initial-scale=1',
title: 'My Learn',
meta: [
{ name: 'description', content: 'My amazing site.' }
]
}
}
})
6.2 组合式API:useHead
useHead 组合式方法允许你使用更加具体和响应式的方法管理头部标签
// pages/head.vue
<script setup lang="ts">
import { ref } from 'vue';
const title = ref('My App')
useHead({
title: title,
meta: [
{ name: 'description', content: 'learn Nuxt' }
],
bodyAttrs: {
class: 'learn'
},
script: [{ children: 'console.log('Hello World')' }]
})
</script>
<template>
<div>
<input type="text" v-model="title">
</div>
</template>
6.3 Components
Nuxt 还提供了 <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html> 和<Head> 标签,可以直接书写在 template 标签内
// pages/componentHead
<template>
<div>
<Head>
<title>{{ title }}</title>
<Meta name="desciption" :content="title" />
<Style type="text/css" children="body { background-color: green; }" />
</Head>
<h1>{{ title }}</h1>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const title = ref('hello')
</script>
6.4 Tpes
以下是对 useHead 以及 app.head 中各个属性类型的定义
interface MetaObject {
title?: string
titleTemplate?: string | ((title?: string) => string)
base?: Base
link?: Link[]
meta?: Meta[]
style?: Style[]
script?: Script[]
noscript?: Noscript[];
htmlAttrs?: HtmlAttributes;
bodyAttrs?: BodyAttributes;
}
6.5 titleTemplate
在上述例子中,我们全局定义了 title: My Learn,这个定义的全局 title,可以在 titleTemplate 中的参数访问到:
// pages/titleTemplate
<script setup lang="ts">
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
}
})
</script>
6.6 Body Tags
<script setup lang="ts">
useHead({
script: [
{
src: 'https://third-party-script.com',
body: true
}
]
})
</script>
6.6 Add External CSS
此内容不在文件中展示
useHead
<script setup lang="ts">
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com'
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
crossorigin: ''
}
]
})
</script>
components
<template>
<div>
<Link rel="preconnect" href="https://fonts.googleapis.com" />
<Link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" crossorigin="" />
</div>
</template>
7. Data Fetching
跳过了 Transitions 章节,想要了解可以自行观看:Transitions · Get Started with Nuxt。
Nuxt 提供了 useFetch、useLazyFetch、useAsyncData 和 useLayAsyncData 去处理项目中数据请求
useFetch, useLazyFetch, useAsyncData 和 useLazyAsyncData 只能工作在 setup 或者生命周期狗子中
在介绍如何获取服务端数据前,我们需要知道,Nuxt 可以被称为全栈框架,因为在 Nuxt 中,我们可以编写服务端代码。
7.1 Server
在根目录中创建 server 文件,API 请求的地址也是采用文件系统。
API 接口示例:
// server/api/count.ts
let count = 0
export default () => {
const runtimeConfig = useRuntimeConfig()
count ++
return JSON.stringify(count)
}
此文件在上文涉及过,不过是演示 runtimeConfig 时
7.2 useFetch
// pages/fetch.vue
<template>
<div>
count: {{ count }}
</div>
</template>
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
7.3 useLazyFetch
这个方法也可以使用 useFetch 实现,只需要在 useFetch 中的配置项添加 lazy: true 即可。
顾名思义,就是懒加载数据的意思,有时候请求数据的速度并不如我们想象的快,如果这个请求数据的方法堵塞了接下来代码的运行,那么用户的体验会很差。
不好演示,就直接写官方给的例子了
<template>
<!-- you will need to handle a loading state -->
<div v-if="pending">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- do something -->
</div>
</div>
</template>
<script setup>
const { pending, data: posts } = useLazyFetch('/api/posts')
watch(posts, (newPosts) => {
// Because posts starts out null, you will not have access
// to its contents immediately, but you can watch it.
})
</script>
7.4 useAsyncData
useAsyncData 和 useFetch 实现的功能基本一致,useFetch(url) 与 useAsyncData(url, () => $fetch(url)) 非常相近,不一样的地方在于 useAsyncData 可以拥有更复杂的逻辑
<script setup>
const { data: count } = await useAsyncData('count', () => $fetch('/api/count'))
</script>
<template>
count: {{ count }}
</template>
7.5 useLazyAsyncData
如 useLazyFetch 同 useFetch 之间的关系一样,由于演示麻烦,这里直接用官方代码
<template>
<div>
{{ pending ? 'Loading' : count }}
</div>
</template>
<script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
watch(count, (newCount) => {
// Because count starts out null, you won't have access
// to its contents immediately, but you can watch it.
})
</script>
7.6 Refreshing Data
有时候用户需要在页面中重新请求数据,这时候就需要重新获取数据的方法,useFetch 就提供了这样的方法,帮助我们重新获取数据
// pages/refresh.vue
<template>
<div>
<div>count: {{ count }}</div>
<button @click="refresh()">重新获取数据</button>
</div>
</template>
<script setup lang="ts">
const { data: count, refresh } = useFetch('/api/count')
</script>
默认情况下,refresh() 会取消所有正在 pending 的请求,他们的结果不会更新,在这个 refresh() 新请求解决之前,任何先前等待的 Promise 都不会解决。不过可以设置 dedupe 选项来取消这一种行为
refresh({ dedupe: true })
7.7 refreshNuxtData
使 Nuxt3 获取数据的方法(useFetch、useLazyAsyncData、useFetch、useLazyFetch)缓存无效并且重新触发 refetch
这个方法适用于你想要重新获取当前页面的所有数据
// pages/lazyAsyncData.vue
<template>
<div>
<div>count: {{ count }}</div>
<div>
<button @click="refresh()">refresh(cache)</button>
<button @click="newRefresh">refresh(invalidateCache)</button>
</div>
</div>
</template>
<script setup>
const { data: count, refresh } = await useAsyncData('count', () => $fetch('/api/count'))
const newRefresh = () => refreshNuxtData('count')
</script>
7.8 clearNuxtData
删除已经缓存的数据、错误信息还有 useAsyncData 和 useFetch 未返回的 promises
如果前往另一个页面并且想要使本页面的数据无效,这个方法很有效
<template>
<div>
<div>count: {{ count }}</div>
<button @click="clearNuxtData('count')">清空数据</button>
</div>
</template>
<script setup lang="ts">
const { data: count } = await useAsyncData('count', () => $fetch('/api/count'))
</script>
7.9 最佳实践
// pages/dataBestPractices
<template>
<h1>{{ mountain?.title }}</h1>
<p>{{ mountain?.description }}</p>
</template>
<script setup lang="ts">
const { data: mountain } = await useFetch(
'/api/mountains/everest',
{ pick: ['title', 'description'] }
)
</script>
// server/api/mountains/everest.ts
const mountainData = {
title: "Mount Everest",
description: "Mount Everest is Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas. The China–Nepal border runs across its summit point",
height: "8,848 m",
countries: [
"China",
"Nepal"
],
continent: "Asia",
image: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/600px-Everest_kalapatthar.jpg"
}
export default () => {
return mountainData
}
8. State Management
Nuxt 提供了 useState 组合方式去创建响应式、对 SSR 友好的共享数据。
useState 对 SSR 是非常用好的,是 ref 的替代者。它的值将在服务器端渲染后(在客户端水混合期间)保留,并使用唯一密钥在所有组件之间共享。
useState只会在 setup 期间或者生命周期钩子中起作用
因为数据在
useState的存储将会被转换为 JSON。所以请不要使用某些不能被转换的关键字,例如classes、functions或者symbols
8.1 最佳实践
不要讲
const state = ref()定义在<script setup>或者setup()函数外这样的做法会导致当用户访问你的网站时, state 一直存在,容易导致内存泄漏
作为替代,请使用
const useX = () => useState(‘x’)
基本示例
<template>
<div>
counter: {{ counter }}
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
8.2 数据共享
// composables/states.ts
export const useCounter = () => useState<number>('couter', () => 0)
export const useColor = () => useState<string>('color', () => 'pink')
// pages/sharedState.vue
<template>
<div>
<div>color: {{ color }}</div>
<div>count: {{ count }}</div>
</div>
</template>
<script setup lang="ts">
const color = useColor()
const count = useCounter()
</script>