vue3
Vue3 学习笔记
第 1 章 Vue3 入门与基础
1.1 Vue3 简介
- 发布时间:2020 年 9 月正式发布,代号 One Piece
- 核心重构:基于 ES6+ 重写,采用 Composition API、Vite 构建、响应式系统重构(Proxy 替代 Object.defineProperty)
- 特点:性能提升(体积减小 40%、渲染快 55%)、更好的 TypeScript 支持、组合式 API、按需编译
- 兼容性:不支持 IE11 及以下,支持现代浏览器
- 应用场景:单页面应用(SPA)、移动端 / H5、后台管理系统、跨端应用(UniApp/TSX)
1.2 环境与开发工具
构建工具:
- Vite:Vue 官方推荐,极速冷启动、按需编译(开发首选)
- Vue CLI:基于 Webpack,兼容老项目,配置繁琐
开发工具(IDE):
- VSCode:轻量免费,必装插件:Volar(Vue3 专属)、TypeScript Vue Plugin
- WebStorm:功能全,对 Vue/TS 支持好,付费
项目初始化:
# Vite 初始化(推荐)
npm create vite@latest my-vue3-project -- --template vue
cd my-vue3-project
npm install
npm run dev
# Vue CLI 初始化(兼容老项目)
npm install -g @vue/cli
vue create my-vue3-project
cd my-vue3-project
npm run serve
1.3 基础语法
模板语法:插值表达式
{{ }}、指令(v-xxx)、属性绑定v-bind/:、事件绑定v-on/@注释:模板中
<!-- 注释 -->,脚本中////* */核心规则:
- 模板只能有一个根节点(Vue3 支持多根,但推荐单根)
- 指令后缀
.modifier(如@click.stop阻止冒泡) - 插值表达式支持简单表达式,不支持语句(如
if/for)
⚠️ 常见语法坑:
- 混淆
v-bind(绑定属性)和v-model(双向绑定) - 忘记
v-for加key导致渲染异常 - 模板中使用未在
setup中暴露的变量
- 混淆
第 2 章 核心概念
2.1 组合式 API(Composition API)
核心优势:逻辑复用、类型友好、代码组织更灵活(替代 Vue2 Options API)
入口函数:
setup(),组件创建前执行,无this指向执行时机:在
beforeCreate之前执行,此时组件实例未创建两种写法:
<!-- 选项式(Options API,兼容写法) --> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } } </script> <!-- 组合式(Composition API,推荐) --> <script setup> import { ref } from 'vue' const count = ref(0) const increment = () => { count.value++ } </script>
2.2 响应式系统
核心原理:基于 ES6 Proxy 实现,支持数组、对象、Map/Set 等所有数据类型
响应式 API 分类:
| API | 用途 | 特点 |
| ————— | —————- | ——————————————- |
| ref() | 基本类型响应式 | 需通过 .value 访问 / 修改,模板中自动解包 |
| reactive() | 引用类型响应式 | 直接访问属性,不支持基本类型 |
| computed() | 计算属性 | 缓存结果,依赖变化才重新计算 |
| watch() | 监听数据变化 | 支持深度监听、立即执行、停止监听 |
| watchEffect() | 自动监听依赖变化 | 立即执行,无需指定监听源 |
- 基础示例:
<script setup>
import { ref, reactive, computed, watch, watchEffect } from 'vue'
// 基本类型响应式
const num = ref(10)
num.value = 20 // 修改
// 引用类型响应式
const user = reactive({ name: '张三', age: 20 })
user.age = 21 // 修改
// 计算属性
const doubleNum = computed(() => num.value * 2)
// 监听
watch(num, (newVal, oldVal) => {
console.log(`num从${oldVal}变为${newVal}`)
}, { immediate: true, deep: true })
// 自动监听
const stop = watchEffect(() => {
console.log(`自动监听:${num.value}`)
})
// 停止监听
// stop()
</script>
⚠️ 响应式坑点:
reactive赋值新对象会失去响应式(解决:用ref包裹对象 /Object.assign)ref在reactive中会自动解包(无需.value)- 数组索引赋值 / 修改长度不会触发响应式(解决:用
push/splice或ref包裹数组)
2.3 模板指令
核心指令:
指令 用途 示例 v-text文本绑定 <div v-text="name"></div>v-html富文本绑定(慎用 XSS) <div v-html="htmlStr"></div>v-bind/:属性绑定 <img :src="imgUrl">v-on/@事件绑定 <button @click="handleClick">v-model双向绑定 <input v-model="username">v-for列表渲染 <li v-for="item in list" :key="item.id">{{item.name}}</li>v-if/v-else条件渲染(销毁 / 创建) <div v-if="flag">显示</div>v-show条件显示(隐藏 / 显示) <div v-show="flag">显示</div>v-slot/#插槽 <slot name="footer"></slot>v-model修饰符:```
- `v-for` 注意事项:
- 必须加唯一 `key`(推荐用 id,避免用 index)
- 避免和 `v-if` 同节点(v-for 优先级更高,性能差)
------
## 第 3 章 组件化开发
### 3.1 组件基础
- 组件类型:
- 全局组件:`app.component('MyComponent', { ... })`
- 局部组件:单文件组件(.vue),导入后使用
- 单文件组件(SFC)结构:
### 3.2 组件通信
| 通信方式 | 适用场景 | 实现方式 |
| -------------- | ----------------------- | ----------------------------------------------- |
| Props | 父 → 子 | 子组件 `defineProps`,父组件属性传递 |
| Emits | 子 → 父 | 子组件 `defineEmits`,父组件事件监听 |
| v-model | 父子双向绑定 | 子组件 `defineModel`(Vue3.4+)/ 自定义 v-model |
| provide/inject | 跨层级(爷 → 孙) | 祖先 `provide` 提供,后代 `inject` 注入 |
| Pinia/Vuex | 全局状态共享 | 全局仓库存储,任意组件访问 |
| 模板引用 | 父访问子组件方法 / 属性 | `ref` 绑定组件,`defineExpose` 暴露内容 |
- 核心示例:
```
3.3 插槽(Slot)
作用:组件内容分发,增强组件灵活性
分类:
- 默认插槽:无名称插槽
- 具名插槽:命名插槽,精准分发
- 作用域插槽:子传父数据,父自定义渲染
<!-- 子组件 SlotDemo.vue -->
<template>
<div>
<!-- 默认插槽 -->
<slot></slot>
<!-- 具名插槽 -->
<slot name="footer"></slot>
<!-- 作用域插槽 -->
<slot name="item" :list="list"></slot>
</div>
</template>
<script setup>
const list = ref([1,2,3])
</script>
<!-- 父组件使用 -->
<template>
<SlotDemo>
<!-- 默认插槽内容 -->
<div>默认插槽内容</div>
<!-- 具名插槽 -->
<template #footer>
<div>页脚插槽内容</div>
</template>
<!-- 作用域插槽 -->
<template #item="slotProps">
<div v-for="item in slotProps.list" :key="item">{{item}}</div>
</template>
</SlotDemo>
</template>
3.4 异步组件
- 作用:按需加载组件,减小首屏体积
- 实现:
defineAsyncComponent
<script setup>
import { defineAsyncComponent } from 'vue'
// 异步加载组件
const AsyncComponent = defineAsyncComponent({
loader: () => import('./AsyncComponent.vue'),
loadingComponent: () => import('./Loading.vue'), // 加载中组件
errorComponent: () => import('./Error.vue'), // 加载失败组件
delay: 200, // 延迟显示加载组件(ms)
timeout: 3000 // 超时时间
})
</script>
<template>
<AsyncComponent />
</template>
第 4 章 路由(Vue Router)
4.1 路由基础
- 版本:Vue3 对应 Vue Router 4.x
- 核心作用:SPA 页面跳转、路由守卫、参数传递
- 安装与配置:
npm install vue-router@4
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// 懒加载路由
component: () => import('../views/About.vue')
},
{
path: '/user/:id', // 动态路由
name: 'User',
component: () => import('../views/User.vue'),
props: true // 开启props传参
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), // Vite 环境变量
routes
})
export default router
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
4.2 路由使用
- 声明式导航:
<template>
<!-- 基础跳转 -->
<router-link to="/">首页</router-link>
<!-- 命名路由跳转 -->
<router-link :to="{ name: 'User', params: { id: 1 } }">用户1</router-link>
<!-- 查询参数跳转 -->
<router-link :to="{ path: '/about', query: { page: 1 } }">关于</router-link>
<!-- 路由出口 -->
<router-view />
</template>
- 编程式导航:
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter() // 路由实例
const route = useRoute() // 当前路由信息
// 跳转页面
const goToUser = () => {
router.push({ name: 'User', params: { id: 2 } })
}
// 替换页面(无历史记录)
const replaceToHome = () => {
router.replace('/')
}
// 前进/后退
const goBack = () => {
router.go(-1) // 后退一步
}
// 获取路由参数
console.log(route.params.id) // 动态参数
console.log(route.query.page) // 查询参数
</script>
4.3 路由守卫
作用:路由跳转拦截(权限校验、登录验证)
分类:
- 全局守卫:
router.beforeEach、router.afterEach - 路由独享守卫:
beforeEnter - 组件内守卫:
onBeforeRouteEnter、onBeforeRouteUpdate
- 全局守卫:
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 验证登录状态
const isLogin = localStorage.getItem('token')
if (to.path === '/admin' && !isLogin) {
next('/login') // 未登录跳登录页
} else {
next() // 放行
}
})
// 组件内守卫
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate } from 'vue-router'
// 进入路由前(无this,用next传参)
onBeforeRouteEnter((to, from, next) => {
next(vm => {
// vm 是组件实例
console.log('进入路由', vm)
})
})
// 路由参数更新时(如 /user/1 → /user/2)
onBeforeRouteUpdate((to, from) => {
console.log('路由参数更新', to.params.id)
})
</script>
第 5 章 状态管理(Pinia)
5.1 Pinia 简介
- 替代 Vuex,Vue 官方推荐,专为 Vue3 设计
- 特点:无 mutations、支持 TS、模块化、轻量化、支持插件
- 安装:
npm install pinia
5.2 核心使用
// src/store/index.js
import { createPinia } from 'pinia'
export const pinia = createPinia()
// src/store/user.js
import { defineStore } from 'pinia'
// 定义仓库(id 唯一)
export const useUserStore = defineStore('user', {
// 状态(替代 state)
state: () => ({
name: '张三',
age: 20,
token: ''
}),
// 计算属性(替代 getters)
getters: {
doubleAge: (state) => state.age * 2,
// 访问其他getter
fullInfo: (state) => `${state.name} - ${state.doubleAge}`
},
// 方法(替代 actions,支持同步/异步)
actions: {
updateName(newName) {
this.name = newName // 直接修改state
},
async login(userInfo) {
// 异步请求
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(userInfo)
})
const data = await res.json()
this.token = data.token // 保存token
},
// 批量修改state
resetState() {
this.$reset() // 重置为初始状态
}
}
})
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { pinia } from './store'
createApp(App).use(pinia).mount('#app')
<!-- 组件中使用 -->
<script setup>
import { useUserStore } from '@/store/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// 方式1:直接访问(响应式)
console.log(userStore.name)
userStore.updateName('李四') // 调用action
// 方式2:解构(需用storeToRefs保持响应式)
const { name, doubleAge } = storeToRefs(userStore)
const { login } = userStore // 方法无需解构
// 批量修改state
const updateState = () => {
userStore.$patch({
age: 25,
name: '王五'
})
// 或函数式(适合复杂修改)
userStore.$patch((state) => {
state.age += 1
})
}
</script>
5.3 模块化
- 按业务拆分仓库(user.js、cart.js、setting.js)
- 仓库间相互调用:
// src/store/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
list: []
}),
actions: {
addItem(item) {
const userStore = useUserStore()
if (userStore.token) { // 访问其他仓库
this.list.push(item)
}
}
}
})
第 6 章 生命周期
6.1 生命周期钩子
Vue3 组合式 API 钩子(替代 Vue2 选项式钩子):
| 组合式 API | 选项式 API | 执行时机 |
| :—————- | :————— | :————————— |
|
onMounted|mounted| 组件挂载完成 ||
onUpdated|updated| 组件更新完成 ||
onUnmounted|unmounted| 组件卸载完成 ||
onBeforeMount|beforeMount| 组件挂载前 ||
onBeforeUpdate|beforeUpdate| 组件更新前 ||
onBeforeUnmount|beforeUnmount| 组件卸载前 ||
onErrorCaptured|errorCaptured| 捕获子组件错误 ||
onActivated|activated| 缓存组件激活时(keep-alive) ||
onDeactivated|deactivated| 缓存组件失活时(keep-alive) |使用示例:
<script setup>
import { onMounted, onUnmounted, onUpdated } from 'vue'
// 挂载完成(常用:DOM操作、请求数据)
onMounted(() => {
console.log('组件挂载完成')
fetchData()
})
// 更新完成
onUpdated(() => {
console.log('组件更新完成')
})
// 卸载前(常用:清除定时器、取消监听)
onBeforeUnmount(() => {
clearInterval(timer)
})
// 卸载完成
onUnmounted(() => {
console.log('组件卸载完成')
})
const fetchData = async () => {
const res = await fetch('/api/data')
const data = await res.json()
console.log(data)
}
</script>
第 7 章 高级特性
7.1 自定义指令
- 作用:扩展模板语法,封装 DOM 操作
- 定义方式:
<!-- 局部指令 -->
<script setup>
// 自定义指令 v-focus
const vFocus = {
mounted(el) {
el.focus() // 挂载后聚焦
}
}
// 简化写法(仅mounted)
// const vFocus = (el) => el.focus()
</script>
<template>
<input v-focus />
</template>
// 全局指令(main.js)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 注册全局指令 v-loading
app.directive('loading', {
mounted(el, binding) {
if (binding.value) {
el.innerHTML = '<div class="loading">加载中...</div>'
} else {
el.innerHTML = ''
}
},
updated(el, binding) {
// 更新时触发
}
})
app.mount('#app')
7.2 组合式函数(Composables)
- 作用:逻辑复用(替代 Vue2 mixins)
- 规范:文件名以
use开头,返回响应式数据 / 方法
// src/composables/useFetch.js
import { ref, watchEffect } from 'vue'
export const useFetch = (url) => {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
loading.value = true
try {
const res = await fetch(url)
data.value = await res.json()
error.value = null
} catch (e) {
error.value = e.message
data.value = null
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchData()
})
return { data, loading, error, fetchData }
}
<!-- 组件中使用 -->
<script setup>
import { useFetch } from '@/composables/useFetch'
const { data, loading, error } = useFetch('/api/list')
</script>
<template>
<div v-if="loading">加载中...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>{{ data }}</div>
</template>
7.3 Teleport(传送门)
- 作用:将组件 DOM 挂载到指定节点(如弹窗、提示框)
<template>
<button @click="showModal = true">打开弹窗</button>
<teleport to="body">
<div class="modal" v-if="showModal">
<div class="modal-content">
<h3>弹窗内容</h3>
<button @click="showModal = false">关闭</button>
</div>
</div>
</teleport>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 4px;
}
</style>
第 8 章 工程化与实战
8.1 Vite 配置
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
// 路径别名
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
// 开发服务器
server: {
port: 3000,
open: true,
proxy: {
// 接口代理
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
minify: 'terser'
}
})
8.2 样式处理
- CSS 预处理器(SCSS/LESS):
npm install sass -D
<style scoped lang="scss">
$primary-color: #42b983;
.hello {
color: $primary-color;
}
</style>
- CSS Modules:
<style module>
.red {
color: red;
}
</style>
<template>
<div :class="$style.red">红色文字</div>
</template>
8.3 生产环境优化
- 路由懒加载(已在路由章节说明)
- 组件异步加载(已在组件章节说明)
- 图片优化:使用
vite-plugin-imagemin压缩图片 - 体积分析:
rollup-plugin-visualizer分析打包体积 - 按需引入:UI 库(如 Element Plus)按需引入
8.4 常见问题排查
- 响应式丢失:检查
reactive赋值新对象、数组索引修改 - 模板报错:检查变量是否暴露、指令语法是否正确
- 路由 404:Vite 配置
base、Nginx 配置try_files $uri $uri/ /index.html - TS 类型错误:检查
defineProps/defineEmits类型定义
附录 :常用 API 速查
| 分类 | 核心 API | 用途 |
|---|---|---|
| 响应式 | ref、reactive、computed、watch | 数据响应式处理 |
| 生命周期 | onMounted、onUpdated、onUnmounted | 组件生命周期管理 |
| 组件通信 | defineProps、defineEmits、defineExpose | 父子组件通信 |
| 路由 | useRouter、useRoute、createRouter | 路由跳转、参数获取 |
| 状态管理 | defineStore、storeToRefs | Pinia 仓库定义、使用 |
| 工具函数 | nextTick、provide、inject | 异步更新、跨层级通信 |
| 高级特性 | defineAsyncComponent、createVNode | 异步组件、手动创建 VNode |
附录 :避坑清单
- 响应式:
reactive赋值新对象失去响应式,用Object.assign或ref包裹 - 模板:
v-for必须加唯一key,避免和v-if同节点 - 组合式 API:
setup中无this,用useRouter/useRoute替代 - 样式:
scoped样式不生效时,检查::v-deep穿透(::v-deep .class) - 路由:动态路由参数更新需监听
onBeforeRouteUpdate - Pinia:解构 state 需用
storeToRefs保持响应式 - 打包:Vite 打包路径问题,配置
base或 Nginx 转发 - 自定义指令:钩子函数参数
el是原生 DOM,不是 Vue 实例
