【笔记】Nuxt学习笔记

前言

Nuxt.js是一个免费且开放源代码的Web应用框架,基于Vue.js、Nitro以及Vite。Nuxt.js的灵感来源于作用类似但基于React.js的框架Next.js。(维基百科)

创建项目

npm

1
2
npx nuxi init <project_name>
cd <project_name>

pnpm

1
2
pnpm dlx nuxi init <project_name>
cd <project_name>

下载依赖

npm

1
npm install

yarn

1
yarn install

pnpm

1
pnpm install --shamefully-hoist

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ assets // 资源
+ components // 组件
+ composables // 组合API
+ layout // 布局
+ pages // 页面
- index.vue // 首页
+ plugins // 插件
+ public // 静态资源目录
- app.vue // 应用程序入口
- app.config.ts // 应用程序配置
- nuxt.config.ts // Nuxt配置
- package.json
- package-lock.json
- README.md
- tsconfig.json // TS配置

基本命令

打包项目

1
nuxt build

打包项目并提前渲染路由

1
nuxt generate

打包项目并在本地预览

1
nuxt preview

运行项目

1
nuxt dev

生成预制文件

  • 生成.next/目录和*.tx文件
  • 在npm install之后会自动执行nuxt prepare
1
nuxt prepare

配置文件

定义配置

  • Nuxt允许省略引入语句

runtimeConfig:运行时配置,可以通过环境变量覆盖

key1:定义在public外部表示仅服务端配置
key2:定义在public内部表示客户端和服务端配置

appConfig:应用配置,构建时变量
app:应用页面配置

app.head:所有页面的<head></head>配置

ssr:是否是SSR项目

true:缺省值,SSR项目
false:SPA项目

router:路由配置

router.options.hashMode:在SPA模式下切换路由模式为Hash模式

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
45
46
47
48
49
50
51
52
53
54
// import { defineNuxtConfig } from "nuxt/config";

export default defineNuxtConfig({
runtimeConfig: {
key1: "value",
public: {
key2: "value"
}
},
appConfig: {
key3: "value"
},
app: {
head: {
title: "",
charset: "UTF-8",
viewport: "",
meta: [
{
name: "keywords",
content: ""
},
{
name: "description",
content: ""
}
],
link: [
{
rel: "",
href: "favicon.ico",
type: "image/x-icon"
}
],
style: [
{
children: "body { color: red }"
}
],
script: [
{
src: "",
body: true
}
]
}
},
ssr: false,
router: {
options: {
hashMode: true
}
}
});

通过环境变量覆盖runtimeConfig运行时配置

  • 使用全大写的、以NUXT_为前缀的蛇形命名环境变量覆盖对应的小驼峰命名的运行时配置
.env
1
2
NUXT_KEY1 = ""
NUXT_PUBLIC_KEY2 = ""

在页面内修改app配置

通过useHead()修改页面配置
1
2
3
4
5
6
7
8
<script setup>
useHead({
title: "",
bodyAttrs: {
class: ""
}
});
</script>
通过SEO组件修改页面配置
1
2
3
4
5
6
7
<template>
<Head>
<Meta name="" content="">
<Style></Style>
<Link></Link>
</Head>
</template>
  • 省略<Head></Head>
1
2
3
4
5
<template>
<Meta name="" content="">
<Style></Style>
<Link></Link>
</template>

使用运行时配置

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
const runtimeConfig = useRuntimeConfig();

if (process.server) {
console.log(runtimeConfig.key1);
console.log(runtimeConfig.public.key2);
}

if (process.client || typeof window === "object") {
console.log(runtimeConfig.public.key2);
}
</script>

使用应用配置

app.vue
1
2
3
4
5
6
7
<script setup>
const appConfig = useAppConfig();

onMounted(function () {
console.log(appConfig.key3); // "value"
});
</script>

内置组件

  • SEO组件
    • <Html>、<Body>、<Head>、<Title>、<Meta>、<Style>、<Link>、<NoScript>、<Base>
  • 欢迎组件,来自@nuxt/ui
    • <NuxtWelcome>
  • 布局组件
    • <NuxtLayout>
  • 占位组件
    • <NuxtPage>
pages/index.vue
1
<template></template>
app.vue
1
2
3
<template>
<NuxtPage></NuxtPage>
</template>
  • 仅客户端渲染组件
    • <ClientOnly>

fallback-tag:预显示的标签,缺省值为span
fallback:预显示的内容

通过属性来配置预渲染

1
2
3
<template>
<ClientOnly fallback-tag="span" fallback="loading..."></ClientOnly>
</template>

通过插槽来配置预渲染

1
2
3
4
5
<template>
<ClientOnly>
<span #fallback>loading...</span>
</ClientOnly>
</template>

内置别名

别名 路径根目录
~、~~、@、@@ 项目根目录
assets 项目根目录/assets
/、public 项目根目录/public

CSS样式

全局CSS样式

在app.vue中定义全局CSS

1
2
3
4
5
<style>
* {
color: red;
}
</style>

在配置文件引入CSS文件作为全局CSS

定义css文件
assets/styles/main.css
1
2
3
* {
color: red;
}
修改配置文件
nuxt.config.ts
1
2
3
export default defineNuxtConfig({
css: ["~/assets/styles/main.css"]
});

局部CSS样式

1
2
3
4
5
<style scope>
* {
color: red;
}
</style>

通过Sass定义CSS样式

下载依赖

1
npm install -D sass

全局CSS样式

在配置文件中引入Sass文件作为全局CSS

assets/styles/main.scss
1
2
3
* {
color: red;
}
nuxt.config.ts
1
2
3
export default defineNuxtConfig({
css: ["~/assets/styles/main.scss"]
});

局部CSS样式

定义Sass文件

assets/styles/variables.scss
1
$mainColor: red;

使用Sass文件

手动引入Sass文件
在Sass文件中引入其他Sass文件
  • 使用命名空间
1
2
3
4
5
@use "~/assets/styles/variables.scss" as vb;

* {
color: vb.$mainColor;
}
  • 省略命名空间
1
2
3
4
5
@use "~/assets/styles/variables.scss" as *;

* {
color: $mainColor;
}
在Vue文件中引入Sass文件
  • 使用命名空间
1
2
3
4
5
6
7
<style scope lang="scss">
@use "~/assets/styles/variables.scss" as vb;

* {
color: vb.$mainColor;
}
</style>
  • 省略命名空间
1
2
3
4
5
6
7
<style scope lang="scss">
@use "~/assets/styles/variables.scss" as *;

* {
color: $mainColor;
}
</style>
通过配置文件自动引入Sass文件
nuxt.config.ts
1
2
3
4
5
6
7
8
9
10
11
export default defineNuxtConfig({
vite: {
css: {
preprocessorOtions: {
scss: {
additionalData: "@use '~/assets/styles/main.scss' as *;"
}
}
}
}
});

资源文件

1
2
3
+ assets
+ images
- img.png

在Vue文件中引入资源文件

  • 直接使用资源文件路径
1
2
3
<template>
<img src="assets/images/img.png" />
</template>
  • 先引入资源文件,再使用资源文件
1
2
3
4
5
6
<template>
<img :src="Img" />
</template>
<script setup>
import Img from "assets/images/img.png";
</script>

在CSS中引入资源文件

1
2
3
* {
background-image: url("assets/images/img.png");
}

字体图标

1
2
3
4
+ assets
+ iconfonts
- iconfont.css
- iconfont.ttf
assets/iconfonts/iconfont.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@font-face {
font-family: "iconfont";
src: url("assets/iconfonts/iconfont.ttf") format("truetype");
}

.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-size: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.icon-home:before {
content: "\e600";
}

修改配置文件

nuxt.config.ts
1
2
3
export default defineNuxtConfig({
css: ["~/assets/iconfonts/iconfont.css"]
});

在Vue文件中引入字体图标

1
2
3
<template>
<i class="iconfont icon-home"></i>
</template>

路由

创建页面

  • 除了手动创建Vue文件实现创建页面,还可以通过脚手架工具创建页面
1
npx nuxi add page <page_name>
1
npx nuxi add page <page_name>/index

静态路由及路由跳转

  • Nuxt的路由采用约定式路由,根据Vue文件名及路径自动定义路由

  • 一级路由

    • 此时访问/home会自动路由到pages/home.vue文件,如果pages/home.vue文件不存在的话自动路由到pages/home/index.vue文件
1
2
3
+ pages
+ home
- index.vue
  • 简写
1
2
+ pages
- home.vue
  • 嵌套路由
    • 定义与父级页面同名的目录用于存放子级页面
1
2
3
4
+ pages
- father.vue
+ father
- son.vue

通过template跳转路由

  • <NuxtPage></NuxtPage>是对router-view的封装
  • <NuxtLink></NuxtLink>是对router-link的封装

to、href:定义跳转目标
replace:是否替换当前路由
active-class:链接为激活状态时的Class,缺省值为router-link-active
target:以何种方式打开新的页面

_self:缺省值,在当前页面打开页面
_blank:在新的页面打开页面

replace:改为替换路由而不是跳转路由
exteral:标记为外部路由跳转,会为HTML标签自动添加rel="noopener noreferrer"属性。对于href属性值是http://或https://开头的外部链接,这个exteral是缺省值

pages/index.vue
1
2
3
4
5
6
7
<template>
<NuxtLink to="/home">
<button>首页</button>
</NuxtLink>

<NuxtPage></NuxtPage>
</template>

query:指定查询字符串

pages/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<NuxtLink :to="{
path: '/home',
query: {
key: 'value'
}
}">
<button>首页</button>
</NuxtLink>

<NuxtPage></NuxtPage>
</template>

通过script跳转路由

有历史记录的跳转
1
2
3
4
5
6
7
8
<script setup>
navigateTo({
path: "/home",
query: {
key: "value"
}
});
</script>
  • 简写
1
2
3
<script setup>
navigateTo("/home");
</script>
无历史记录的跳转
1
2
3
4
5
6
7
8
9
10
<script setup>
navigateTo({
path: "/home",
query: {
key: "value"
}
}, {
replace: true
});
</script>
  • 简写
1
2
3
<script setup>
navigateTo("/home", { replace: true });
</script>
跳转到外部
1
2
3
<script setup>
navigateTo("https://example.com", { external: true });
</script>
useRouter()

传送门

1
2
3
4
<script setup>
const router = useRouter();
router.push("/home");
</script>

动态路由及路由跳转

1
2
3
+ pages
+ home
- [key].vue

通过script跳转路由

pages/home/[id].vue
1
2
3
4
<script setup>
const route = useRoute();
const { key } = route.params;
</script>

多级路由

1
2
3
4
+ pages
- home.vue
+ home
- index.vue

404页面

1
2
+ pages
- [...slug].vue
pages/[...slug].vue
1
<template>404</template>

查询字符串参数

1
http://example.com/?key=value
1
2
3
4
<script setup>
const route = useRoute();
const { key } = route.query;
</script>

路由中间件

  • 中间件函数如果返回的是零值,则继续下一个中间件
  • 如果返回的是navigateTo()则跳转到对应路由,不会继续下一个中间件
  • 如果返回的是{ statusCode: 404, statusMessage: "error" }则表示设置响应码和响应描述,响应描述只能是英文字符串

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

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
definePageMeta({
middleware: [
function (to, from) {
...
},
function (to, from) {
...
}
]
});
</script>

命名路由中间件

middleware/home.ts
1
2
3
export default defineNuxtRouteMiddleware(function (to, from) {
...
});
1
2
3
4
5
<script setup>
definePageMeta({
middleware: ["home"]
});
</script>

全局中间件

  • 文件名以.global.ts结尾的中间件是全局中间件,每个页面无需手动引入
  • 全局中间件优先级最高
middleware/home.global.ts
1
2
3
export default defineNuxtRouteMiddleware(function (to, from) {
...
});

为不同路由设置不同渲染模式

ssr: true:SSR
ssr: fale:SPA
static: true:只会在构建时生成一次静态页面
swr: true:自动重新生成静态页面,间隔时间缺省值为1秒

nuxt.config.ts
1
2
3
4
5
6
7
8
export default defineNuxtConfig({
routeRules: {
"/": { ssr: true },
"/home": { ssr: fale },
"/page": { static: true },
"/log": { swr: true }
}
});

布局

  • 布局是页面的包装器

默认布局的定义和使用

layouts/default.vue
1
2
3
4
5
6
7
<template>
<div class="header"></div>
<div class="main">
<slot></slot>
</div>
<div class="footer"></div>
</template>

name:定义使用的布局

default:缺省值,使用默认布局

app.vue
1
2
3
4
5
<template>
<NuxtLayout name="default">
<NuxtPage></NuxtPage>
</NuxtLayout>
</template>

当前页面使用其他的布局

  • 不指定使用什么布局,默认使用默认布局
app.vue
1
2
3
4
5
<template>
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</template>
  • 在某个页面指定这个页面使用的布局
pages/home.vue
1
2
3
4
5
<script setup>
definePageMeta({
layout: ""
});
</script>

插件

定义插件

在app.vue中定义插件

app.vue
1
2
3
4
5
6
7
<script setup>
const nuxtApp = useNuxtApp();
nuxtApp.provider("fn", function () {
...
});
nuxtApp.provider("key", "value");
</script>

在plugins目录内定义插件

  • 使用.ts作为插件的文件名后缀时,在服务端渲染和客户端渲染时都可以使用
  • 使用.server.ts作为插件的文件名后缀时,只有在服务端渲染时才可以使用
  • 使用.client.ts作为插件的文件名后缀时,只有在客户端渲染时才可以使用
  • 使用数字.作为插件的文件名前缀时,插件的加载时具有顺序(正序)
plugins/.ts
1
2
3
4
5
6
export default defineNuxtPlugin(function (nuxtApp) {
return {
fn: function () {},
key: "value"
};
});

使用插件

1
2
3
4
5
<script setup>
const nuxtApp = useNuxtApp();
nuxtApp.$fn();
console.log(nuxtApp.$key);
</script>

生命周期函数

通过app.vue定义生命周期函数

app.vue
1
2
3
4
5
6
<script setup>
const nuxtApp = useNuxtApp();
nuxtApp.hook("app:mounted", function (vueApp) {
...
});
</script>

通过插件定义生命周期函数

plugins/lifecycle.ts
1
2
3
4
5
6
7
export default defineNuxtPlugin(function (nuxtApp) {
nuxtApp.hook("app:created", function (vueApp) {});
nuxtApp.hook("app:beforeMount", function (vueApp) {});
nuxtApp.hook("vue:setup", function () {});
nuxtApp.hook("app:rendered", function (renderContext) {});
nuxtApp.hook("app:mounted", function (vueApp) {});
});

组件的生命周期函数

  • 在服务端渲染时
    • 如果是OptionsAPI只会回调beforeCreate()函数和created()函数
    • 如果是CompositionAPI只会回调setup()函数
1
2
3
4
5
6
7
8
9
10
11
12
<script>
export default {
setup: function () {},
onBeforeMount: function () {},
onMounted: function () {},
onBeforeUpdate: function () {},
onUpdated: function () {},
onBeforeUnmount: function () {},
onUnmounted: function () {},
onErrorCaptured: function () {}
}
</script>

发送请求

<key>:用于解析异步数据的唯一键,如果没有指定,缺省值为文件名+行号

query、params:查询字符串参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup>
const { data } = await useAsyncData("<key>", function () {
return $fetch("<url>", {
method: "GET",
baseURL: "https://example.com",
query: {
key: "value"
},
body: {
key: "value"
},
headers: {
key: "value"
},
onRequest: function ({ request, options }) {},
onRequestError: function ({ request, options, error }) {},
onResponse: function ({ request, response, options }) {},
onResponseError: function ({ request, response, options, error }) {}
});
});
</script>
  • 简写
1
2
3
<script setup>
const { data } = await useFetch("<url>", { method: "GET" });
</script>

懒加载

lazy:懒加载

false:缺省值,不开启懒加载,不开启懒加载时会阻塞页面渲染
true:开启懒加载,开启懒加载时不会阻塞页面渲染,可以使用watch()监测数据改变

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
const { data } = await useAsyncData("<key>", function () {
return $fetch("<url>", {
method: "GET",
lazy: true
});
});

watch(data, function (newData) {
...
});
</script>
  • 简写
1
2
3
4
5
6
7
8
9
10
<script setup>
const { data } = await useFetch("<url>", {
method: "GET",
lazy: true
});

watch(data, function (newData) {
...
});
</script>
  • 对于开启了懒加载的请求可以继续简写
1
2
3
4
5
6
7
8
9
<script setup>
const { data } = await useLazyFetch("<url>", {
method: "GET"
});

watch(data, function (newData) {
...
});
</script>

刷新请求

1
2
3
4
5
6
7
8
9
<script setup>
const { data, refresh } = await useAsyncData("<key>", function () {
return $fetch("<url>", {
method: "GET"
});
});

refresh();
</script>

判断请求状态

pending:请求状态,如果是false则表示请求结束,如果时true表示正在请求

1
2
3
4
5
6
7
8
9
<script setup>
const { data, pending } = await useAsyncData("<key>", function () {
return $fetch("<url>", {
method: "GET"
});
});

if (pending) {}
</script>

设置Cookie

maxAge:定义存活时间,单位秒

1
2
3
4
<script setup>
const cookie = useCookie("key", { maxAge: 10 });
cookie.value = "value";
</script>

获取Cookie

  • 服务器和客户端都可以获取到Cookie
1
2
3
4
<script setup>
const cookie = useCookie("key");
const value = cookie.value;
</script>

服务器

  • 以服务器的身份处理请求,返回响应,可以用于Mock模拟数据

  • 使用.ts作为API的文件名后缀时,能够同时处理所有请求方法

  • 使用.get.ts作为API的文件名后缀时,只能处理GET请求方法

server/api/home.ts
1
2
3
4
5
6
export default defineEventHandle(function (event) {
return {
code: 200,
data: "value"
}
});
request
1
GET http://127.0.0.1:80/api/home

请求对象

获取请求方法

1
const result = event.node.req.method;

获取请求URL

1
const result = event.node.req.url;

全局处理方法

获取请求方法

1
const result = getMethod(event);

获取查询字符串

1
const result = getQuery(event);

获取请求体

1
const result = await readBody();

获取请求体原始数据

1
const result = await readRawBody();

自定义Hook

  • 自定义Hook在服务器和客户端都有效

定义Hook

  • 如果使用匿名导出自定义Hook,则Hook名为文件名
composables/useMyHook.ts
1
2
3
export default function () {
...
}
  • 如果使用命名导出自定义Hook,则Hook名为函数变量名
composables/useMyHook.ts
1
2
3
export const useMyHook = function () {
...
}

使用Hook

1
2
3
<script setup>
const myHook = useMyHook();
</script>

状态管理

通过自定义Hook实现状态管理

  • useState中的值函数返回值不支持返回class类、function函数、symbol符号
composables/useMyHook.ts
1
2
3
4
5
export default function () {
return useState("key", function () {
return "value";
});
}
1
2
3
4
5
6
7
<template>
{{ myHook }}
</template>

<script setup>
const myHook = useMyHook();
</script>

Pinia

下载依赖

1
npm install pinia @pinia/nuxt

修改配置文件

nuxt.config.ts
1
2
3
export default defineNuxtConfig({
modules: ["@pinia/nuxt"]
});

定义Store

store/home.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineState } from "pinia";

export default useHomeStore = defineState("home", {
state: function () {
return {
key: "default"
}
},
actions: {
fn: function () {
this.key = "value";
}
}
});

使用状态

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { storeToRefs } from "pinia";
import { useHomeStore } from "~/store/home";

const homeStore = useHomeStore();
const { key } = storeToRefs(homeStore);
console.log(key); // "default"

homeStore.fn();
console.log(key); // "value"
</script>

完成