前言
之前用过nuxt2,nuxt3出来之后还没来得及看,最近终于有点空闲的时间,继续开始卷了,于是参考官网一点点搭建,最大的感受是框架就是用来解放人的双手的,绝大部分的场景框架都为你考虑到了,不再需要自己手动实现,反而有点不习惯了😑
nuxt3安装
- node: 16.19.0
- npmp && npx: 8.19.3
安装
npx nuxi init nuxt3-prod
# 进入nuxt-prod目录安装依赖
npm i
运行
npm run dev
😴 如果出现下面错误,说明安装依赖时出现了错误,先确定本地的npm源,然后重新安装
🍕 下面则是运行成功的界面
nuxt3配置
目录结构
nuxt3项目初始化后的目录结构可以说是相当的简单
- .nuxt: nuxt自动编译生成的
- app.vue: 主入口文件
- nuxt.config.ts: nuxt配置文件
- package.json: 包管理文件
配置文件
nuxt.config.ts
nuxt.config.js为项目默认的配置文件,配置的某些属性可以在应用中访问到
export default defineNuxtConfig({
runtimeConfig: {
// 服务器端可以获取使用
apiSecret: "123456",
// 客户端获取使用
public: {
apiBase: "/api",
},
},
});
访问方式app.vue
<script setup>
const runtimeConfig = useRuntimeConfig();
console.log(runtimeConfig);
</script>
app.config.ts
与nuxt.config.js类似的一个全局配置文件,但是需要手动创建,并且必须在根目录下面,里面定义的对象可以全局访问,相对于一个全局变量对象
export default defineAppConfig({
title: "Hello world",
theme: {
colors: {
primary: "#ff0000",
},
},
});
访问方式app.vue
<script setup>
const appConfig = useAppConfig();
console.log(appConfig);
</script>
视图界面
组件
在根目录创建components文件夹,然后再建立驼峰式的组件文件(也可以创建驼峰文件夹,子文件默认为index.vue),nuxt会自动引入创建的所有文件并全局注册,在任意地方都可以直接使用
路由
默认的项目模板是没有路由的,我们需要手动创建pages文件夹,然后在其下面建立文件,nuxt会把里面的文件当做路由文件进行配置,index.vue为默认路由入口文件,此时有两种方式可以进入到路由
第一种:修改app.vue代码
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
在模块中新增<NuxtPage />
组件, 默认为路由的入口
第二种:根目录新增layouts文件夹,然后再建立子文件default.vue
<template>
<slot></slot>
</template>
此方式需要删除app.vue文件, default的名称不可修改,nuxt会默认读取
布局
需要在根目录新建layouts文件夹,default.vue为其默认的布局组件,这样pages中所有路由都会被它包裹,通常我们把界面公共的布局区域放到此处,如头部导航,底部菜单等...
<template>
<Header></Header>
<slot></slot>
<Footer></Footer>
</template>
并不是所有界面都用到默认的布局,如果还需要其他布局,可以在layouts目录下新增组件,比如custom.vue
<template>
<div>我是新的自定义头部</div>
<slot></slot>
</template>
当一个路由界面需要用此自定义布局时, 在路由中配置即可,没有配置的默认使用default.vue
<template>
<div>我是新的界面,使用自定义布局</div>
</template>
<script setup>
defineMetaPage({
layout: 'custom'
})
</script>
资源
public
public目录主要用作静态资源公共服务器,在代码中可以直接访问到静态资源文件, 此目录的文件不会被构建工具所编译
比如在public目录下有个img文件夹,放一个logo.png文件
<img src="/img/logo.png" />
assets
静态资源的管理目录,主要用在客户端,希望能被构建工具(Vite或webpack)处理的资源放在这个目录下
此目录不像layoust,components,它不会被nuxt自动处理,所以此目录下的文件没有严格的命令规范
比如在assets目录下有个img文件夹,放一个logo.png
<img src="~/assets/img/logo.png" />
全局样式
在配置文件中引入即可...
export default defineNuxtConfig({
...,
css: ['~/assets/css/index.css', '~/assets/css/base.css']
});
路由
路由配置
在pages目录下新建路由文件,nuxt会自动的引入所有文件生成路由配置,跳转的路径就是文件名称
-
pages
- index.vue
- list.vue
const router = useRouter()
router.push('/')
router.push('/list')
嵌套路由
如果需要用到嵌套路由,则需创建一个与路由文件名称相同的文件夹, 如home.vue路由有嵌套路由,则创建一个同名的文件夹,在定义路由界面即可...
-
pages
- home
- index.vue
- ohter.vue
- index.vue
- home.vue
- home
在nuxt3中嵌套路由使用<NuxtChild />组件貌似不行了,需要使用<NuxtPage />
<template>
<NuxtPage />
</template>
<script setup>
const router = useRouter()
router.push('/home)
router.push('/home/other)
</script>
动态路由
1. 文件以中括号命名
在文件夹下建立以[id].vue命令的组件文件, 不能以下划线_id.vue命名,会访问不了
-
pages
- posts
- [id].vue
- index.vue
- posts
<script setup>
// 传递一个参数
const router = useRouter()
router.push('/posts/1)
router.push('/posts/123)
</script>
<script setup>
// 接收参数
const route = useRoute()
console.log(route.params.id)
</script>
2. 嵌套以中括号命令文件夹和文件
-
pages
- posts
- [type]
- [id].vue
- [type]
- index.vue
- posts
<script setup>
// 传递两个参数
const router = useRouter()
router.push('/posts/a/1)
router.push('/posts/b/123)
</script>
<script setup>
// 接收参数
const route = useRoute()
console.log(route.params.type, route.params.id)
</script>
3. 文件夹和文件以中括号命名
-
pages
- posts-[type]
- [id].vue
- index.vue
- posts-[type]
<script setup>
// 传递两个参数
const router = useRouter()
router.push('/posts-a/1)
router.push('/posts-b/123)
</script>
<script setup>
// 接收参数
const route = useRoute()
console.log(route.params.type, route.params.id)
</script>
4. 文件以[...]命令
-
pages
- posts
- [...all].vue
- index.vue
- posts
<script setup>
// 此方式没有任何的参数限制
const router = useRouter()
router.push('/posts/a/1)
router.push('/posts/b/123)
router.push('/posts/b/123/32323)
</script>
<script setup>
// 接收参数
const route = useRoute()
console.log(route.params.all[0], route.params.all[1], route.params.all[2])
</script>
路由跳转
1. 组件跳转
<template>
<nav>
<ul>
<li><NuxtLink to="/">home</NuxtLink></li>
<li><NuxtLink to="/about">about</NuxtLink></li>
</ul>
</nav>
</template>
2. 方法跳转
<script>
const router = useRouter()
router.push('/about')
</script>
路由参数
通过?拼接参数
<script setup>
// 路由跳转
const router = useRouter()
router.push('/detail?id=123&name=hello')
</script>
<script setup>
// 接受参数
const route = useRoute()
console.log(route.query.id, route.query.name)
</script>
通过动态路由参数传递, 需要配置成动态路由
参考上面动态路由跳转和参数接收
路由中间件
1. 命令路由中间件
定义在middleware文件夹中,当页面每次使用时,会通过异步导入自动加载执行
middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
console.log("要去那个页面:" + to.path);
console.log("来自那个页面:" + from.path);
})
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
<template>
<h1>我是首页使用了路由中间件</h1>
</template>
2. 全局路由中间件
定义在middleware文件夹中, 使用.global.js
后缀, 在每次路由变化时自动执行。
middleware/auth.global.js
export default defineNuxtRouteMiddleware((to, from) => {
console.log("要去那个页面:" + to.path);
console.log("来自那个页面:" + from.path);
});
路由验证
Nuxt通过每个要验证界面中的definePageMeta()方法的vilidate属性进行验证,vildata方法接收route作用参数,可以返回一个boolean值来确定此路由是否为有效路由,如果为false,并且找不到其他匹配项,则会导致404
<script setup lang="ts">
definePageMeta({
validate: async (route) => {
// 检查id是否由数字组成
return /^\d+$/.test(route.params.id)
}
})
</script>
SEO配置
head全局配置
在nuxt.config
文件中全局配置
export default defineNuxtConfig({
app: {
head: {
charset: "UTF-8",
viewport: "width=device-width, initial-scale=1.0",
title: "加速器",
meta: [{ name: "description", content: "加速器" }],
},
},
})
动态配置
在路由界面中使用useHead函数配置
<script setup>
const title = '首页'
useHead({
title,
meta: [ { name: 'description', content: 'My amazing site.' } ]
});
</script>
composables组合式
自动扫描
此目录下的所有顶层文件会被自动扫描,文件中导出的方法,在应用程序中可以被直接使用,不用再手动引入
创建文件composables/useDateTime.js
export const getYear = () => {
return new Date().getFullYear()
}
在app.vue
中使用
<template>
<div>
{{ year }}
</div>
</template>
<script setup>
const year = getYear()
</script>
嵌套文件
如果composables中有嵌套的文件夹,默认情况下它里面的方法是不会被扫描到的,用两种方式可以实现
1. 对嵌套的文件重新导出
对于项目的目录:composables/index.js
composables/myUtils/utils.js
,我们可以在index中引入utils并直接导出, 这种方式不太优雅
export { utils } from './myUtils/utils.ts'
2. 在nuxt.config.ts文件中配置,会遍历查找
export default defineNuxtConfig({
imports: {
dirs: [
// 扫描顶层模块
'composables',
// 扫描嵌套的模块,并且使用特定的名称和后缀的文件
'composables/*/index.{ts,js,mjs,mts}',
// 扫描给定目录内的所有模块
'composables/**'
]
}
})
useState状态管理
Nuxt3提供了可组合的useState,以创建跨组件和SSR友好的共享数据状态,利用此特性也可实现全局的状态管理(客户端不是可以持久化)
简单使用
在单个路由中使用的时候类似于ref的功能,创建的值可以作为全局的唯一标识,在任何其他的路由组件中使用
<template>
<div>
<div>{{ counter }}</div>
<div @click="counter++">counter++</div>
<div @click="counter--">counter--</div>
</div>
</template>
<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
其他路由组件中使用
<template>
<div>
<div>{{ counter }}</div>
</div>
</template>
<script setup>
const counter = useState('counter')
</script>
状态共享
在componsables目录下创建文件userStore.js
export const useUserInfo = () =>
useState("userInfo", () => {
return {
nickName: "tony",
};
});
在其他组件中使用
<template>
<div>
{{ userName }}
</div>
<div @click="changeName">改变值</div>
</template>
<script setup>
const userInfo = useUserInfo();
const changeName = () => {
userInfo.userName = 'hi tony'
}
</script>
pinia状态管理
安装依赖
npm i pinia @pinia/nuxt @pinia-plugin-persistedstate/nuxt -S
pinia配置
nuxt.congfig
中配置
modules: ["@pinia/nuxt", "@pinia-plugin-persistedstate/nuxt"],
pinia使用
在根目录创建pinia/user.js
模块, 可以任意的创建多个模块
import { defineStore } from "pinia";
export default defineStore("user", {
state() {
return {
userName: 'hello world',
};
},
getters: {},
actions: {
setUserName(userName) {
this.userName = userName;
},
}
});
路由中使用
<template>
<div>{{ userName }}</div>
</template>
<script setup>
import { toRefs } from 'vue'
import userPinia from '~/pinia/user'
const userStore = userPinia()
const { userName } = toRefs(userStore)
const changeName = () => {
userStore.setUserName('hi world')
}
</script>
数据持久化
安装依赖
npm i @pinia-plugin-persistedstate/nuxt -S
模块配置
import { defineStore } from "pinia";
export default defineStore("user", {
state() {
return {
userName: 'hello world',
};
},
getters: {},
actions: {
setUserName(userName) {
this.userName = userName;
},·
},
persist: {
key: "user", // 存储的标识符,默认为store的值(第一个参数)
paths: ["userName"], // 存储哪些数据
},
});
sass配置
安装sass
cnpm i sass -D
sass全局变量
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "assets/css/var.scss";`,
},
},
},
},
数据请求
nuxt3中提供了4个方法来获取数据, 他们可以在路由,组件或插件中使用
注意:他们只能在setup
或者是生命周期钩子
中使用
useFetch
该方法实际上是对 useAsyncData 和$fetch 的封装,提供了一个更便捷的封装方法,在服务器端获取数据使用
<script setup>
const headers = useRequestHeaders(["cookie"]);
const { data: count } = await useFetch('/api/count',{headers})
</script>
<template>
Page visits: {{ count }}
</template>
$fetch
主要用于在客户端用户交互时的数据请求, 此方法在pages的路由界面setup中不能被直接调用,在子组件的setup中可以被直接调用(客户端调用)
<script setup>
const sendData = () => {
const { data } = await $fetch('/api/send')
}
</script>
<template>
<div @click="sendData">发送数据</div>
</template>
<script setup>
const { data } = await $fetch('/api/send')
console.log(data)
</script>
<template>
</template>
useLazyFetch
相当于useFetch
方法默认配置了lazy:true
,执行异步函数时不会阻塞路由的执行。也意味着你必须处理数据为null
的情况
<template>
<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>
useAsynaData
server/api/count.ts
let counter = 0
export default () => {
counter++
return JSON.stringify(counter)
}
app.vue
<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
useLazyAsyncData
<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>
element-plus配置
安装
配置
插件
在根目录plugins下创建antd.client.js
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Antd)
});
使用
<template>
<a-button type="primary">按钮</a-button>
</template>
基础配置文件
export default defineNuxtConfig({
app: {
head: {
charset: "UTF-8",
viewport: "width=device-width, initial-scale=1.0",
title: "首页",
meta: [{ name: "description", content: "首页" }],
},
},
modules: ["@pinia/nuxt", "@pinia-plugin-persistedstate/nuxt"],
runtimeConfig: {
// 服务器端可以获取
apiSecret: "123",
// 客户端获取
public: {
apiBase: "/api",
},
},
css: [
"~/assets/css/base.css",
"~/assets/css/flex.css",
],
vite: {
css: {
preprocessorOptions: {
less: {
additionalData: '@import "assets/css/var.less";',
},
},
},
},
});
开发构建命令
命令 | 功能 |
---|---|
npm run dev | 启动本地开发服务器 |
npm run build | 使用混合渲染模式创建一个.output目录,其中包含所有应用程序、服务器和依赖项,可用于生产 |
npm run genarate | 使用SPA方式预渲染应用程序的每个路由,将结果存储在纯 HTML 文件中,可以部署在任何静态托管服务上,注意:服务端渲染的内容都可正常访问(已知$fetch请求数据会异常);客户端渲染如果涉及到访问Nuxt的server api等情况会不正常,访问外部数据API正常,请上线前完整验证 |
npm run preview | build构建打包后,启动本地预览服务 |
npm run postinstall | 在应用程序中创建.nuxt目录并生成类型(没用过) |
部署
构建打包
npm run build
pm2部署
在根目录创建一个配置文件ecosystem.config.js
module.exports = {
apps: [
{
name: 'nuxt3-prod',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs'
}
]
}
把.output和配置文件上传到服务器,然后使用pm2启动,目录结构可以自己调整
pm2 start ecosystem.config.js // 启动服务
pm2 list // 查看是否启动成功
设置自动启动
当服务器挂了重新恢复后,之前的node服务还在
pm2 startup