文章目录
- 一、Vue3 核心特性与环境搭建
- 1.1 Vue3 核心升级点
- 1.2 开发环境搭建
- 方式 1:Vite(推荐,极速构建)
- 方式 2:Vue CLI
- 项目结构核心文件
- 入口文件差异(Vue2 vs Vue3)
- 二、组合式 API 全解析:语法与实战
- 2.1 setup 语法糖(核心入口)
- 2.2 核心 API 用法
- 2.2.1 状态定义:ref 与 reactive
- 2.2.2 计算属性:computed
- 2.2.3 监听器:watch 与 watchEffect
- 2.3 生命周期钩子
- 2.4 逻辑复用:自定义 Hooks
- 三、响应式系统:原理与 API 用法
- 3.1 响应式原理对比
- 3.2 响应式工具 API
- 3.2.1 只读代理:readonly
- 3.2.2 响应式转换:toRef 与 toRefs
- 3.2.3 非响应式标记:markRaw
- 四、组件进阶:新特性与通信方案
- 4.1 组件新特性
- 4.1.1 多根节点组件(Fragment)
- 4.1.2 Teleport(传送门)
- 4.1.3 Suspense(异步组件加载)
- 4.2 组件通信方案汇总
- 4.2.1 父子通信:props 与 emit
- 4.2.2 跨级通信:provide 与 inject
- 4.2.3 兄弟通信:mitt 事件总线
- 五、实战案例:TodoList 全流程实现
- 5.1 项目结构
- 5.2 状态管理:useTodoStore.js
- 5.3 组件实现
- TodoInput.vue
- TodoItem.vue
- TodoList.vue
- App.vue(根组件整合)
- 六、避坑指南与性能优化
- 6.1 常见问题与解决方案
- 6.2 性能优化技巧
- 6.2.1 减少不必要的渲染
- 6.2.2 大型列表优化
一、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)// 修改值需通过.valueage.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 = nullonMounted(() => {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 })// 单个属性转换为refconst nameRef = toRef(user, 'name')// 所有属性转换为refconst { 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/awaitconst 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>// 定义接收的propsconst 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"><inputv-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 }"><inputtype="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"><TodoItemv-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><RecycleScrollerclass="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>