Vue3基础知识汇总与实战指南 - 教程
文章目录
一、Vue3 核心特性与环境搭建
1.1 Vue3 核心升级点
Vue3 在语法、性能、扩展性上实现全面升级,核心变化包括:
组合式 API:按业务逻辑聚合代码,替代 Vue2 选项式 API 的分散写法
响应式重构:基于 Proxy 实现,支持数组索引、对象新增属性的响应式追踪
多根节点组件:无需外层包裹 div,减少 DOM 层级
新组件支持:Teleport、Suspense、TransitionGroup 等增强组件能力
TypeScript 原生支持:类型推导更完善,开发体验提升
1.2 开发环境搭建
方式 1:Vite(推荐,极速构建)
# 创建项目
npm create vite@latest vue3-demo -- --template vue
# 进入项目
cd vue3-demo
# 安装依赖
npm install
# 启动开发服务
npm run dev
方式 2:Vue CLI
# 安装CLI(需Node.js 14.18+)
npm install -g @vue/cli
# 创建项目
vue create vue3-demo
# 选择Vue 3选项
# 启动服务
cd vue3-demo && npm run serve
项目结构核心文件
vue3-demo/
├── src/
│ ├── main.js # 入口文件(替换Vue2的new Vue())
│ ├── App.vue # 根组件(支持多根节点)
│ └── components/ # 组件目录
入口文件差异(Vue2 vs Vue3)
// Vue2入口
import Vue from 'vue'
import App from './App.vue'
new Vue({ el: '#app', render: h => h(App) })
// Vue3入口(main.js)
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
二、组合式 API 全解析:语法与实战
2.1 setup 语法糖(核心入口)
Vue3.2 + 推荐的简化写法,无需 return 即可暴露变量 / 方法到模板,自动注册组件。
<!-- 基础用法 -->
<template>
<button @click="count++">{{ count }}</button>
</template>
<script setup>
// 直接定义响应式变量
import { ref } from 'vue'
const count = ref(0)
</script>
2.2 核心 API 用法
2.2.1 状态定义:ref 与 reactive
<script setup>
import { ref, reactive, toRefs } from 'vue'
// 1. 基础类型响应式(ref)
const age = ref(20)
// 修改值需通过.value
age.value = 21
// 2. 对象类型响应式(reactive)
const user = reactive({
name: '张三',
address: { city: '北京' }
})
// 直接修改属性
user.name = '李四'
// 3. 解构响应式对象(toRefs)
const { name, address } = toRefs(user)
</script>
2.2.2 计算属性:computed
<script setup>
import { ref, computed } from 'vue'
const score = ref(85)
// 只读计算属性
const level = computed(() => {
return score.value >= 90 ? '优秀' : '良好'
})
// 可写计算属性
const doubleScore = computed({
get() { return score.value * 2 },
set(val) { score.value = val / 2 }
})
doubleScore.value = 180 // 触发set,score变为90
</script>
2.2.3 监听器:watch 与 watchEffect
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const user = ref({ name: '张三' })
// 1. 监听单个基础类型
watch(count, (newVal, oldVal) => {
console.log(`从${oldVal}变到${newVal}`)
}, { immediate: true }) // 立即执行
// 2. 监听对象属性
watch(() => user.value.name, (newName) => {
console.log('用户名变更:', newName)
})
// 3. 自动追踪依赖(watchEffect)
watchEffect(() => {
console.log('count值:', count.value)
})
</script>
2.3 生命周期钩子
Vue3 生命周期需显式导入,setup 替代了 Vue2 的 beforeCreate 和 created。
<script setup>
import { onMounted, onBeforeUnmount } from 'vue'
// 组件挂载后执行
onMounted(() => {
console.log('DOM已挂载')
})
// 组件卸载前清理
let timer = null
onMounted(() => {
timer = setInterval(() => console.log('计时'), 1000)
})
onBeforeUnmount(() => {
clearInterval(timer)
})
// 支持多个同名钩子顺序执行
onMounted(() => {
console.log('第二个mounted钩子')
})
</script>
2.4 逻辑复用:自定义 Hooks
替代 Vue2 的 mixin,解决命名冲突和依赖模糊问题。
// hooks/useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
function updatePosition(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
return { x, y }
}
<!-- 组件中使用 -->
<template>
鼠标位置:{{x}}, {{y}}
</template>
<script setup>
import useMousePosition from './hooks/useMousePosition'
const { x, y } = useMousePosition()
</script>
三、响应式系统:原理与 API 用法
3.1 响应式原理对比
| 特性 | Vue2(Object.defineProperty) | Vue3(Proxy) |
|---|---|---|
| 追踪范围 | 仅对象已定义属性 | 全属性(含新增 / 删除) |
| 数组支持 | 需重写 7 种方法(push/pop 等) | 原生支持数组索引 / 长度修改 |
| 嵌套响应 | 递归定义需额外处理 | 自动深度代理 |
| 性能 | 初始化慢(遍历所有属性) | 懒代理(访问时才代理) |
3.2 响应式工具 API
3.2.1 只读代理:readonly
<script setup>
import { reactive, readonly } from 'vue'
const user = reactive({ name: '张三' })
const readOnlyUser = readonly(user)
// 修改会警告且无效
readOnlyUser.name = '李四' // 控制台警告
</script>
3.2.2 响应式转换:toRef 与 toRefs
<script setup>
import { reactive, toRef, toRefs } from 'vue'
const user = reactive({ name: '张三', age: 20 })
// 单个属性转换为ref
const nameRef = toRef(user, 'name')
// 所有属性转换为ref
const { name, age } = toRefs(user)
</script>
3.2.3 非响应式标记:markRaw
<script setup>
import { reactive, markRaw } from 'vue'
// 标记后对象不再响应式
const rawObj = markRaw({ id: 1 })
const state = reactive({
data: rawObj
})
state.data.id = 2 // 不会触发更新
</script>
四、组件进阶:新特性与通信方案
4.1 组件新特性
4.1.1 多根节点组件(Fragment)
<!-- Vue3支持直接写多个根节点 -->
<template>
<header>头部</header>
<main>主体</main>
<footer>底部</footer>
</template>
4.1.2 Teleport(传送门)
将组件内容渲染到指定 DOM 位置,解决层级样式问题(如弹窗)。
<template>
<button @click="show = true">打开弹窗</button>
<teleport to="body">
<div class="dialog" v-if="show">
<h3>弹窗标题</h3>
<button @click="show = false">关闭</button>
</div>
</teleport>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
4.1.3 Suspense(异步组件加载)
等待异步组件加载时显示兜底内容,优化用户体验。
<!-- 父组件 -->
<template>
<suspense>
<!-- 默认插槽:异步组件加载完成后显示 -->
<template #default>
<AsyncComponent />
</template>
<!-- 兜底插槽:加载中显示 -->
<template #fallback>
<div>加载中...</div>
</template>
</suspense>
</template>
<script setup>
// 动态导入异步组件
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
<!-- AsyncComponent.vue(含异步逻辑) -->
<script setup>
// setup支持async/await
const fetchData = async () => {
const res = await fetch('/api/data')
return res.json()
}
const data = await fetchData()
</script>
4.2 组件通信方案汇总
4.2.1 父子通信:props 与 emit
<!-- 子组件 Child.vue -->
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
// 定义接收的props
const props = defineProps({
title: { type: String, required: true }
})
// 定义触发的事件
const emit = defineEmits(['change'])
const handleClick = () => {
emit('change', '传递给父组件的值')
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<Child title="父传子标题" @change="handleChange" />
</template>
<script setup>
const handleChange = (val) => {
console.log('子传父的值:', val)
}
</script>
4.2.2 跨级通信:provide 与 inject
<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('light')
// 提供数据
provide('theme', theme)
// 提供修改方法
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
// 注入数据和方法
const theme = inject('theme')
const updateTheme = inject('updateTheme')
// 调用方法修改祖先组件数据
const switchTheme = () => {
updateTheme(theme.value === 'light' ? 'dark' : 'light')
}
</script>
4.2.3 兄弟通信:mitt 事件总线
# 安装mitt
npm install mitt
// utils/eventBus.js
import mitt from 'mitt'
export const eventBus = mitt()
<!-- 组件A(发送方) -->
<script setup>
import { eventBus } from './utils/eventBus'
const sendMsg = () => {
eventBus.emit('msg', '来自A的消息')
}
</script>
<!-- 组件B(接收方) -->
<script setup>
import { eventBus } from './utils/eventBus'
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
// 监听事件
eventBus.on('msg', (val) => {
console.log('接收消息:', val)
})
})
onUnmounted(() => {
// 解绑事件
eventBus.off('msg')
})
</script>
五、实战案例:TodoList 全流程实现
5.1 项目结构
src/
├── components/
│ ├── TodoInput.vue # 输入框组件
│ ├── TodoList.vue # 列表组件
│ └── TodoItem.vue # 列表项组件
├── hooks/
│ └── useTodoStore.js # 状态管理Hook
├── App.vue # 根组件
└── main.js # 入口文件
5.2 状态管理:useTodoStore.js
import { ref, computed } from 'vue'
export default function useTodoStore() {
// 本地存储初始化
const todos = ref(JSON.parse(localStorage.getItem('todos') || '[]'))
// 计算属性:未完成数量
const unDoneCount = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
// 方法:添加任务
const addTodo = (text) => {
if (!text.trim()) return
todos.value.push({
id: Date.now(),
text,
done: false
})
}
// 方法:切换任务状态
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.done = !todo.done
}
// 方法:删除任务
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id)
}
// 监听todos变化,同步到本地存储
watch(todos, (newTodos) => {
localStorage.setItem('todos', JSON.stringify(newTodos))
}, { deep: true })
return { todos, unDoneCount, addTodo, toggleTodo, deleteTodo }
}
5.3 组件实现
TodoInput.vue
<template>
<div class="input-container">
<input
v-model="text"
@keyup.enter="handleAdd"
placeholder="请输入任务"
/>
<button @click="handleAdd">添加</button>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const text = ref('')
const emit = defineEmits(['add'])
const handleAdd = () => {
emit('add', text.value)
text.value = '' // 清空输入框
}
</script>
TodoItem.vue
<template>
<li :class="{ done: todo.done }">
<input
type="checkbox"
:checked="todo.done"
@change="$emit('toggle', todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="$emit('delete', todo.id)">删除</button>
</li>
</template>
<script setup>
defineProps({
todo: {
type: Object,
required: true,
properties: {
id: Number,
text: String,
done: Boolean
}
}
})
defineEmits(['toggle', 'delete'])
</script>
<style scoped>
.done span {
text-decoration: line-through;
color: #999;
}
</style>
TodoList.vue
<template>
<ul class="todo-list">
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle="toggleTodo"
@delete="deleteTodo"
/>
</ul>
</template>
<script setup>
import TodoItem from './TodoItem.vue'
defineProps({
todos: { type: Array, required: true },
toggleTodo: { type: Function, required: true },
deleteTodo: { type: Function, required: true }
})
</script>
App.vue(根组件整合)
<template>
<div class="app">
<h1>TodoList(剩余{{ unDoneCount }}项)</h1>
<TodoInput @add="addTodo" />
<TodoList
:todos="todos"
:toggleTodo="toggleTodo"
:deleteTodo="deleteTodo"
/>
</div>
</template>
<script setup>
import TodoInput from './components/TodoInput.vue'
import TodoList from './components/TodoList.vue'
import useTodoStore from './hooks/useTodoStore'
// 导入状态管理逻辑
const { todos, unDoneCount, addTodo, toggleTodo, deleteTodo } = useTodoStore()
</script>
六、避坑指南与性能优化
6.1 常见问题与解决方案
| 问题场景 | 原因分析 | 解决方案 |
|---|---|---|
| ref 对象修改不更新 DOM | 直接赋值替换了 ref 引用 | 改用.value 修改属性(如 refObj.value = 10) |
| 异步组件报错 | async setup 未用 Suspense 包裹 | 父组件添加 Suspense 组件兜底 |
| provide/inject 数据不响应 | 传递的是非响应式值 | 用 ref/reactive 包装后再 provide |
| toRefs 解构后失去响应 | 直接解构 reactive 对象 | 必须通过 toRefs 转换后再解构 |
| 组件卸载后定时器仍运行 | 未在 onBeforeUnmount 清理 | 定时器需在卸载前 clearInterval |
6.2 性能优化技巧
6.2.1 减少不必要的渲染
<!-- 1. 组件缓存:KeepAlive -->
<template>
<KeepAlive>
<router-view /> <!-- 缓存路由组件 -->
</KeepAlive>
</template>
<!-- 2. 计算属性缓存:避免重复计算 -->
<script setup>
import { computed } from 'vue'
// 复杂计算会被缓存,仅依赖变化时重新计算
const complexResult = computed(() => {
return heavyCalculation(state.data)
})
</script>
<!-- 3. 事件处理函数缓存:避免每次渲染创建新函数 -->
<script setup>
import { useCallback } from 'vue'
// 缓存函数,仅依赖变化时重新创建
const handleClick = useCallback((id) => {
console.log('点击了', id)
}, []) // 空依赖数组:始终返回同一函数
</script>
6.2.2 大型列表优化
<!-- 使用vue-virtual-scroller实现虚拟滚动 -->
<template>
<RecycleScroller
class="scroller"
:items="largeList"
:item-size="50"
>
<template v-slot="{ item }">
<div class="list-item">{{ item.content }}</div>
</template>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
// 模拟10万条数据
const largeList = Array(100000).fill(0).map((_, i) => ({
id: i,
content: `列表项 ${i + 1}`
}))
</script>

浙公网安备 33010602011771号