1. 为什么现在学Vue3正当时
前端框架的迭代速度总是快得让人喘不过气,但Vue3的发布绝对是近年来最值得关注的里程碑事件。作为从Vue2一路走来的老玩家,我清楚地记得2020年9月那个晚上,当我第一次用createApp替换掉new Vue()时那种既熟悉又陌生的兴奋感。两年多过去,现在Vue3的生态已经足够成熟,各大UI库基本完成适配,周边工具链也趋于完善,正是入门的最佳时机。
与React的hooks和Svelte的编译时方案不同,Vue3选择了一条独特的渐进式增强路线。组合式API(Composition API)的引入不是对选项式API(Options API)的否定,而是提供了另一种代码组织方式。这种设计哲学让Vue3呈现出惊人的灵活性——你可以继续用熟悉的data和methods写法完成简单需求,也可以在复杂场景下享受组合式API带来的模块化优势。
2. 环境搭建与项目初始化
2.1 现代前端开发的基石:Vite
放弃webpack吧,至少在入门阶段没必要和自己过不去。Vite的出现彻底改变了前端工具的体验,它的即时服务器启动和闪电般的热更新速度,能让学习过程变得愉悦。安装只需一行命令:
bash复制npm create vite@latest my-vue-app --template vue
这个命令会创建一个标准的Vue3项目结构,其中几个关键文件需要特别注意:
main.js:应用入口,现在使用createApp工厂函数App.vue:根组件,单文件组件(SFC)的模板vite.config.js:构建配置,比webpack简洁十倍
重要提示:如果遇到ESLint报错,建议暂时关闭严格模式。在
vite.config.js中添加:js复制defineConfig({ eslint: { lintOnStart: false } })
2.2 组件的基本骨骼结构
打开App.vue,你会看到Vue3单文件组件的经典三段式结构:
html复制<script setup>
// 逻辑区
</script>
<template>
<!-- 视图层 -->
</template>
<style scoped>
/* 作用域样式 */
</style>
这个<script setup>语法糖是Vue3的王牌特性之一,它让组件的编写变得极其简洁。与传统写法相比,它自动完成了以下几件事:
- 顶层绑定自动暴露给模板
- 无需手动返回
setup()中的响应式数据 - 直接支持await异步操作
3. 响应式系统的核心机制
3.1 ref与reactive的抉择
Vue3的响应式系统完全重写,基于ES6的Proxy实现,性能提升显著。新手最容易困惑的就是ref和reactive的选择:
| 特性 | ref | reactive |
|---|---|---|
| 适用场景 | 基本类型/对象引用 | 复杂对象 |
| 访问方式 | 需要.value | 直接访问属性 |
| 模板解包 | 自动解包 | 无需解包 |
| TS支持 | 类型推断更精确 | 嵌套对象类型可能丢失 |
实际开发中我的经验法则是:
- 数字、字符串等简单类型用
ref - 表单对象等复杂结构用
reactive - 需要保持引用的场景用
ref包裹reactive
javascript复制const count = ref(0) // 基本类型
const form = reactive({ // 复杂对象
username: '',
password: ''
})
const user = ref({ // 保持引用
name: 'Alice',
age: 25
})
3.2 计算属性与侦听器的实战技巧
计算属性(computed)和侦听器(watch)是处理衍生数据的两种方式,它们的区别就像Excel中的公式和宏:
javascript复制const doubleCount = computed(() => count.value * 2)
watch(count, (newVal, oldVal) => {
console.log(`计数从${oldVal}变为${newVal}`)
}, { immediate: true })
几个容易踩坑的点:
- 计算属性应该是纯函数,不要在里面做异步操作或副作用
- 深度侦听对象时使用
watch(() => ({...obj}), callback) - 多个侦听器可以用
watchEffect统一管理
4. 组件通信的现代方案
4.1 Props与Events的基础强化
父子组件通信虽然基础,但Vue3的TypeScript支持让它有了新玩法:
html复制<!-- 子组件 -->
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
validator: v => v.length > 3
}
})
const emit = defineEmits<{
(e: 'update', payload: number): void
}>()
</script>
最佳实践:复杂props建议使用PropType进行类型定义:
ts复制import type { PropType } from 'vue' defineProps({ config: Object as PropType<{ size: number }> })
4.2 依赖注入的进阶用法
跨层级组件通信可以用provide/inject替代props透传,Vue3的响应式注入让这个模式更强大:
javascript复制// 祖先组件
const theme = ref('dark')
provide('theme', readonly(theme))
// 后代组件
const theme = inject('theme', 'light') // 默认值
安全提示:注入的值应该考虑使用readonly包装,除非你确实需要子组件修改它。
5. 状态管理的轻量级方案
5.1 组合式函数的魔法
对于中小型应用,完全可以用组合式函数替代Pinia/Vuex:
javascript复制// useCounter.js
export default function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
在组件中使用:
html复制<script setup>
import useCounter from './useCounter'
const { count, increment } = useCounter()
</script>
5.2 共享状态的管理模式
多个组件需要共享状态时,可以提升状态到共同祖先,或者使用简单store模式:
javascript复制// store.js
import { reactive } from 'vue'
export const store = reactive({
user: null,
setUser(user) {
this.user = user
}
})
这种模式在50个组件以下的中型应用中表现良好,超过这个规模再考虑Pinia。
6. 模板指令的现代用法
6.1 v-model的华丽变身
Vue3的v-model迎来了重大升级,现在支持多个v-model绑定:
html复制<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
对应的组件内部:
javascript复制defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
6.2 条件渲染的性能优化
v-if和v-show的选择一直是个经典问题,Vue3的编译优化让它们的差异更明显:
v-if:真正的条件渲染,适合运行时条件很少变化的场景v-show:只是CSS切换,适合频繁切换的场景(如选项卡)
新增的<Transition>组件让动画实现变得简单:
html复制<Transition name="fade" mode="out-in">
<div v-if="show" key="content">...</div>
</Transition>
配套CSS:
css复制.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
7. 生命周期钩子的变化
Vue3的生命周期虽然概念相似,但有几个重要变化:
beforeDestroy→beforeUnmountdestroyed→unmounted- 新增
renderTracked和renderTriggered调试钩子
组合式API下的使用方式:
javascript复制import { onMounted } from 'vue'
onMounted(() => {
console.log('组件挂载完成')
})
特别提醒:在<script setup>中,setup()本身就是beforeCreate和created的阶段,所以不需要显式定义这两个钩子。
8. 实战技巧与性能优化
8.1 模板引用(ref)的妙用
获取DOM元素或组件实例时,模板ref比querySelector优雅得多:
html复制<template>
<input ref="inputRef">
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
8.2 性能优化三剑客
-
浅层响应式:对于大型不可变数据,使用
shallowRef或shallowReactivejavascript复制const bigList = shallowRef([]) // 内部变化不会触发更新 -
手动控制依赖:使用
markRaw跳过响应式转换javascript复制const foo = markRaw({}) // 永远不会被代理 -
虚拟滚动:长列表使用
<vue-virtual-scroller>等方案
9. TypeScript集成指南
Vue3对TypeScript的支持是革命性的,几个关键配置:
-
在
tsconfig.json中添加:json复制{ "compilerOptions": { "types": ["vite/client"], "strict": true } } -
定义组件props的几种方式:
typescript复制// 运行时声明 defineProps({ msg: String }) // 基于类型的声明 defineProps<{ msg: string count?: number }>() -
自定义事件类型:
typescript复制const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>()
10. 常见问题排雷手册
-
响应式丢失问题:
- 解构props会失去响应性,使用
toRefs保持响应
javascript复制const { title } = toRefs(props) - 解构props会失去响应性,使用
-
循环引用警告:
- 在组件内部修改props时,Vue会发出警告
- 正确的做法是触发事件让父组件修改
-
异步组件加载:
javascript复制const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) -
动态组件缓存:
html复制<KeepAlive include="CompA,CompB"> <component :is="currentComponent" /> </KeepAlive> -
自定义指令变化:
Vue3中指令的生命周期钩子与组件对齐:bind→beforeMountinserted→mounted- 新增
beforeUpdate和updated
从Vue2迁移过来的开发者需要特别注意这些变化点,我在团队内部维护了一个详细的对照表,帮助成员平滑过渡。记住,Vue3的学习曲线前期可能稍陡,但一旦掌握组合式API的思维方式,开发效率会有质的飞跃。