项目搭建
生成目录结构
pnpm dlx nuxi@latest init <project-name>
添加Eslint配置
下载依赖
pnpm i @antfu/eslint-config -D
配置文件
-
创建配置文件
eslint.config.js
import antfu from '@antfu/eslint-config' export default antfu( /** * 第一层配置选项,用于启用 UnoCSS、Vue 和 TypeScript 的格式化处理。 */ { unocss: true, // 启用 UnoCSS 支持 // 对 vue 文件进行格式化处理 vue: true, // 对 ts 文件进行格式化处理 typescript: true, formatters: { // 对 css 文件进行格式化处理 css: true, // 对 html 文件进行格式化处理 html: true, }, }, /** * 第二层配置选项,用于指定文件匹配模式和 Vue 特定的规则。 */ { // 指定匹配所有 .vue 文件 files: ['**/*.vue'], rules: { 'vue/html-self-closing': ['error', { html: { // 要求 HTML 空元素始终自闭合 void: 'always', }, }], }, }, /** * 第三层配置选项,用于定义 ESLint 忽略检查的文件和目录。 */ { ignores: [ // 忽略 assets 目录下的 font 文件夹 'assets/font', // 忽略 node_modules 目录 'node_modules', // 忽略 .output 目录 '.output', // 忽略 .nuxt 目录 '.nuxt', // 忽略 dist 目录 'dist', ], }, )
-
package.json
添加启动脚本
{
"lint": "eslint .",
"lint:fix": "eslint . --fix",
}
添加.husky配置
生成项目.git
git init
自动配置husky
npx husky-init
- 生成.husky目录
-
创建
scripts
命令行{ "prepare": "husky install", }
这个脚本的作用是在 npm 安装过程结束时自动安装和配置 Husky,以便在 Git 事件发生时自动执行预设的脚本
具体来说,
"prepare": "husky install"
这个脚本的作用如下:- 触发时机:当执行
npm install
或npm ci
命令时,npm 会在安装完所有依赖后自动执行prepare
脚本。 - 执行 Husky 安装:
husky install
命令会在项目中安装 Husky,并设置必要的 Git 钩子。这通常包括:- 在项目的
.git
目录下创建一个husky
子目录。 - 在
husky
目录中创建钩子文件,如pre-commit
、pre-push
等。 - 将 Husky 的可执行文件路径添加到这些钩子文件中,以便在相应的 Git 事件发生时执行 Husky。
- 在项目的
- 触发时机:当执行
安装依赖
pnpm i
Lint-staged 增量检测提交代码
下载依赖
yarn add lint-staged -D
配置文件
- 创建
lint-staged.config.js
/**
* Lint-staged 配置文件,用于在 Git 提交前自动格式化和修复代码。
* 这个配置文件通常命名为 .lintstagedrc 或在 package.json 中作为 lint-staged 属性。
*/
/**
* 定义对不同文件类型执行的 linting 规则。
* 每个规则都指定了一组 glob 模式,用于匹配文件,以及一个命令列表,
* 这些命令将在匹配的文件被暂存(staged)时执行。
*/
export default {
/**
* 对 JavaScript、TypeScript、JSX 和 TSX 文件执行 ESLint 修复。
* 这些文件在被暂存时会自动修复 ESLint 可自动修复的问题。
*/
'*.{js,ts,jsx,tsx}': ['eslint --fix'],
/**
* 对 JSON 文件执行 ESLint 修复。
* 这些文件在被暂存时会自动修复 ESLint 可自动修复的问题。
*/
'*.json': ['eslint --fix'],
/**
* 对 Vue 文件执行 ESLint 修复。
* 这些文件在被暂存时会自动修复 ESLint 可自动修复的问题。
*/
'*.vue': ['eslint --fix']
}
- 创建
scripts
命令行
{
"lint:lint-staged": "lint-staged"
}
- 修改
.husky/pre-commit
提交前钩子命令
pnpm run lint:lint-staged --allow-empty
Commitlint
下载依赖
pnpm i @commitlint/cli @commitlint/config-conventional -D
配置文件
-
添加
commitlint.config.js
文件module.exports = { // 继承的规则 extends: ['@commitlint/config-conventional'], // @see: https://commitlint.js.org/#/reference-rules rules: { 'subject-case': [0], // subject大小写不做校验 // 类型枚举,git提交type必须是以下类型 'type-enum': [ 2, 'always', [ 'feat', // 新增功能 'fix', // 修复缺陷 'docs', // 文档变更 'style', // 代码格式(不影响功能,例如空格、分号等格式修正) 'refactor', // 代码重构(不包括 bug 修复、功能新增) 'perf', // 性能优化 'test', // 添加疏漏测试或已有测试改动 'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等) 'ci', // 修改 CI 配置、脚本 'revert', // 回滚 commit 'chore' // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) ] ] } }
- 修改
.husky/commit-msg
钩子用于 git 提交信息校验
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"
- 修改
SSR原理
注意:只有在首次地址请求的时候,会请求到SSR服务端,后续的路由跳转都是前端跳转。因为首次的请求不仅会返回HTML结构,还会返回Vue等数据
nuxt.config.ts
配置
export default defineNuxtConfig({
/**
* compatibilityDate 是一个配置项,用于指定 Nuxt 应该兼容的日期。
* 这个日期用于确定 Nuxt 应该使用哪些特性和行为。
* 通常,这个日期越新,Nuxt 会使用更多的新特性和优化,但也可能意味着对旧版本浏览器的支持会减少。
* 在这个例子中,'2024-11-01' 表示 Nuxt 将尽可能使用 2024 年 11 月 1 日之后引入的特性和优化。
*/
compatibilityDate: '2024-11-01',
/**
* devtools 是一个配置对象,用于控制 Nuxt DevTools 的行为。
* Nuxt DevTools 是一个浏览器扩展,提供了 Nuxt 应用的调试工具。
*/
devtools: {
/**
* true 表示启用 Nuxt DevTools。
* 当这个选项设置为 true 时,如果你已经安装了 Nuxt DevTools 浏览器扩展,它将自动连接到你的 Nuxt 应用,允许你在开发过程中进行调试。
*/
enabled: true,
},
/**
* 引入全局的css文件
*/
css: [
'@/styles/global.css',
]
})
添加unocss
下载依赖
pnpm add -D unocss @unocss/nuxt
配置文件
-
创建
uno.config.js
配置文件import { defineConfig, presetUno, } from 'unocss' export default defineConfig({ presets: [ presetUno(), ], })
-
在
nuxt.config.js
文件中引入模块export default defineNuxtConfig({ modules: [ '@unocss/nuxt', ], })
添加Element Plus
下载依赖
pnpm i element-plus @element-plus/nuxt -D
配置文件
- 在
nuxt.config.js
文件中引入模块
export default defineNuxtConfig({
modules: [
'@element-plus/nuxt'
],
elementPlus: { /** Options */ }
})
scss全局导入
下载依赖
pnpm add -D sass-embedded
引入全局文件
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/global.scss" as *;`,
},
},
},
},
工具方法
会自动导入composables
和utils
目录内的所有向外暴露的工具函数
export function showToast() {
console.log('展示提示框')
}
export function hideToast() {
console.log('隐藏提示框')
}
可以在任意位置使用
<template>
<div @click="showToast">
展示
</div>
<div @click="hideToast">
隐藏
</div>
</template>
插件
plugins
目录内定义插件,会自动第一层目录下文件内容
-
定义全局指令、组件
-
设置全局生命周期钩子
-
安装第三方插件、依赖包
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:loading:start', () => {
console.log('新页面加载开始')
})
nuxtApp.hook('app:mounted', () => {
console.log('app mounted')
})
nuxtApp.vueApp.directive('toast', {
mounted() {
console.log('渲染了toast指令')
},
})
nuxtApp.vueApp.use({
install() {
console.log('安装插件')
},
})
})
注意:插件内容只会在首次加载服务端运行,后续的路由跳转都不会执行
项目路由
路由地址
-
创建
pages
目录,会根据目录中文件名称,生成路由跳转规则 -
在
app.vue
添加页面入口<template> <NuxtPage /> </template>
基础路由
生成/home
、/login
、/
路由
注意:index.vue
被转换的路由地址是/
,而不是/index
嵌套路由
任意路由
文件名称:[...任意名称].vue
,比如[...404].vue
在所有路由都不满足的情况,跳转到该路由
多层路由
app.vue
<template>
<header>app页面头部</header>
<NuxtPage />
<footer>app页面底部</footer>
</template>
news.vue
<template>
<header>新闻页面头部</header>
<NuxtPage />
<footer>新闻页面底部</footer>
</template>
add.vue
<template>
<h1>新闻添加页面</h1>
</template>
路由参数
动态路由
<template>
<h1>新闻详情页面</h1>
<h2>新闻ID: {{ useRoute().params.id }}</h2>
</template>
可选路由
/user/settings
和/settings
访问的页面是一样的
<template>
<h1>用户设置页面</h1>
<h2>{{ useRoute().params.user }}</h2>
</template>
*注意:[]
和[[]]
*都代表了可选参数,不通的点是,参数是否能为空
路由配置
<script setup lang='ts'>
definePageMeta({
// 路由路径
path: '/home.html',
// 路由别名
alias: ['/home'],
})
</script>
<template>
<h1>home页面</h1>
</template>
路由导航
标签式
<template>
<NuxtLink to="/home">
home
</NuxtLink>
</template>
编程式
useRouter
<script setup>
useRouter().push('/user')
</script>
<template>
<h1>home页面</h1>
</template>
navigateTo
<script setup>
navigateTo('/user')
</script>
<template>
<h1>home页面</h1>
</template>
useRouter
和navigateTo
都能实现页面跳转
useRouter
在服务端不会被识别,直接会被略过执行,也就是useRouter
跳转只会发生在客户端
navigateTo
在服务端和客户端都会发生,在服务端就是重定向至跳转页面,客户端和useRouter
功能相同
路由守卫
所有的路由守卫都创建在middleware
目录中,Nuxt
会自动导入守卫
页面路由
// toast.js
import { defineNuxtRouteMiddleware } from '#app'
export default defineNuxtRouteMiddleware((to) => {
console.log('路由中间件', to.fullPath)
})
<script setup>
definePageMeta({
path: '/index',
// 使用路由守卫
middleware: [
'toast',
],
})
</script>
<template>
<h2>Index页面</h2>
</template>
全局路由
在middleware
目录下,创建包含.global
的文件名称,那么就会创建全局路由守卫
// token.global.js
import { defineNuxtRouteMiddleware, navigateTo } from '#app'
import { tokenStore } from '@/composables/use-token'
import { ElMessage } from 'element-plus'
export default defineNuxtRouteMiddleware((to) => {
const fullPath = to.fullPath
const noLogin = ['/login']
if (import.meta.client) {
if (!noLogin.includes(fullPath)) {
if (!tokenStore().isLogin) {
ElMessage({
message: '请先登录',
type: 'warning',
})
return navigateTo('/login')
}
}
}
})
全局数据
runtimeConfig
export default defineNuxtConfig({
// 定义全局数据
runtimeConfig: {
// 只有服务端能访问
isServer: true,
// public内的数据,客户端和服务端都能访问
public: {
baseUrl: 'http://127.0.0.1:3000',
},
},
})
const config = useRuntimeConfig()
/**
* 服务端:true
* 客户端:undefined
*/
console.log(config.isServer)
/**
* 服务端:http://127.0.0.1:3000
* 客户端:http://127.0.0.1:3000
*/
console.log(config.public.baseUrl)
服务端和客户端判断
runtimeConfig
在客户端只能访问runtimeConfig.public
内部的数据
<script setup>
const config = useRuntimeConfig()
if (config.isServer) {
console.log('当前是服务端输出')
}
else {
console.log('当前是客户端输出')
}
</script>
<template>
<h1>home页面</h1>
</template>
import.meta.server
和import.meta.client
<script setup>
if (import.meta.server) {
console.log('当前是服务端输出')
}
if (import.meta.client) {
console.log('当前是客户端输出')
}
</script>
<template>
<h1>home页面</h1>
</template>
SSR数据共享
<script setup>
const count = useState(() => 0)
count.value++
console.log(count.value)
</script>
<template>
<h1>home页面</h1>
<NuxtLink to="/user">
home
</NuxtLink>
</template>
如果使用const count = ref(0)
定义数据,那么在服务端和客户端输出的都是0,这是因为两服务端和客户端count
变量都进行重新初始化
通过useState
不仅可以多路由共享数据,还可以服务端和客户端共享数据
pinia
集成
pnpm i pinia @pinia/nuxt
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
})
使用
在composables
目录定义user-count.js
文件
import { defineStore } from 'pinia'
export const countStore = defineStore('count', {
state: () => {
return {
count: 0,
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
},
})
任意页面使用
<template>
<h2>
count: {{ countStore().count }}
</h2>
<button @click="countStore().increment">
增加
</button>
<button @click="countStore().decrement">
减少
</button>
</template>
数据持久化
pnpm add pinia-plugin-persistedstate
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'pinia-plugin-persistedstate/nuxt',
],
})
初始化数据
import { createPinia, defineStore } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export const countStore = defineStore('count', {
state: () => {
return {
count: 0,
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
},
persist: {
storage: import.meta.client ? localStorage : null,
},
})
当想把storage设置为localStorage
、sessionStorage
配置时
- 需要判断是在客户端环境,因为服务端环境没有这些配置
- 只有把保存到
cookie
,客服端和服务端才能共享数据
多页面监听
watchStorageChange() {
if (import.meta.client) {
window.addEventListener('storage', (event) => {
if (event.key === 'count') {
const newValue = JSON.parse(event.newValue)
if (newValue?.count) {
this.count = newValue.count
}
}
})
}
},
在actions
中加入监听,在任意页面执行就可以了
网络请求
接口定义
在server
目录下,可以创建测试接口
// 注意:这里来在 "h3",而不是 "#app"
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return {
code: 200,
message: 'success',
data: {
name: 'John Doe',
email: 'john@doe.com',
},
}
})
该文件的路由地址是get请求/api/user-info
,文件名称指定请求方式
发起请求
$fetch
初始情况下:客户端和服务端都会发送请求
前端路由切换:客户端会发送请求
客户端函数执行:客户端会发送请求
<script setup>
const infoData = await $fetch('/api/info')
console.log(infoData)
</script>
<template>
<h1>Index页面</h1>
</template>
useAsyncData
初始情况下:只有服务端会发送请求
前端路由切换:客户端会发送请求
客户端函数执行:客户端会发送请求
<script setup>
const infoData = await useAsyncData(() => $fetch('/api/info'))
console.log(infoData)
async function loadInfoData() {
const infoData = await useAsyncData(() => $fetch('/api/info'))
console.log(infoData)
}
</script>
<template>
<el-button @click="loadInfoData">
请求接口
</el-button>
</template>
useFetch
功能和useAsyncData
使用同样的
<script setup>
const infoData = await useFetch('/api/info')
console.log(infoData)
async function loadInfoData() {
const infoData = await useFetch('/api/info')
console.log(infoData)
}
</script>
<template>
<el-button @click="loadInfoData">
请求接口
</el-button>
</template>