🆚 主要区别
| 特性 | Vue 2(选项式 API) | Vue 3(组合式 API) |
|---|---|---|
| 逻辑组织方式 | 按选项分类(如data、methods、computed 等) | 按功能逻辑分组(通过 setup() 或 <script setup>) |
| 代码复用 | 依赖 Mixins 或高阶组件(容易导致命名冲突和逻辑分散) | 使用 Composable 函数(更清晰、可复用性强) |
| 响应式系统 | 基于 Object.defineProperty 实现 |
基于 Proxy 实现,性能更好、功能更强 |
| TypeScript 支持 | 支持有限,类型推导不够完善 | 原生支持 TypeScript,类型推导更友好 |
| 编程范式 | 命令式为主 | 声明式 + 函数式 |
一、Vue 2 的痛点:Mixins 的"甜蜜陷阱"
在 Vue 2 中,当我们需要复用一段逻辑时,最常见的做法是使用 Mixins(混入) 。它的使用非常简单:只需要把公共逻辑抽离成一个对象,然后通过 mixins 属性注入到组件中即可。
javascript
const myMixin = {
data() {
return { message: 'Hello' }
},
methods: {
greet() {
console.log(this.message)
}
}
}
export default {
mixins: [myMixin],
mounted() {
this.greet() // 输出 "Hello"
}
}
看似方便,但 Mixins 却埋下了不少隐患:
- 命名冲突:如果两个 Mixins 定义了同名属性或方法,后一个会默默覆盖前一个,导致难以排查的 bug。
- 逻辑碎片化:随着组件变复杂,逻辑会被分散到多个 Mixins 中,最终让人摸不清头绪。
- 调试困难:组件中的数据来源模糊不清,增加了维护成本。
这些问题让 Mixins 成为了"甜蜜的负担"。
二、Vue 3 的革新:Composable 函数登场
Vue 3 彻底改变了逻辑复用的方式,推出了 Composable 函数 。这是一种基于函数式编程思想的设计,核心理念是"把逻辑封装成独立的函数,按需调用"。
来看一个经典的例子------计数器:
typescript
import { ref } from 'vue'
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
export default {
setup() {
const { count, increment, decrement } = useCounter(10)
return { count, increment, decrement }
}
}
这段代码的优点显而易见:
- 所有关于计数器的逻辑都集中在
useCounter函数中,结构清晰; - 调用时只需解构返回值,无需担心命名冲突;
- 可以轻松复用到其他组件中,真正做到"一次编写,多处使用"。
Composable 函数的本质其实就是纯函数:输入确定则输出确定,没有副作用,逻辑高度解耦。
三、响应式系统的蜕变:从 defineProperty 到 Proxy
除了逻辑复用,Vue 3 还对底层的响应式系统进行了彻底重构。
Vue 2 的响应式原理
Vue 2 使用 Object.defineProperty 来劫持对象属性的变化:
javascript
const obj = {}
Object.defineProperty(obj, 'name', {
get() {
console.log('读取 name')
return this._name
},
set(value) {
console.log('设置 name:', value)
this._name = value
}
})
这种方式虽然实现了基本的响应式功能,但也存在明显缺陷:
- 无法监听新增属性 :必须通过
$set手动触发更新; - 数组索引赋值失效 :如
arr[0] = newValue不会被监听; - 性能开销大:需要递归遍历所有属性。
Vue 3 的响应式原理
Vue 3 换用了 ES6 的 Proxy,它可以拦截整个对象的所有操作:
javascript
const target = { name: 'Vue' }
const proxy = new Proxy(target, {
get(target, key) {
console.log(`读取 ${key}`)
return target[key]
},
set(target, key, value) {
console.log(`设置 ${key}: ${value}`)
target[key] = value
return true
}
})
Proxy 的优势在于:
- 自动支持新增属性和删除属性;
- 数组操作也能完美监听;
- 性能更好,采用惰性初始化策略。
这种改进不仅提升了开发体验,也让 Vue 3 的响应式系统更加健壮。
四、背后的思想:函数式编程的魅力
Vue 3 的设计深受 函数式编程 的影响。那什么是函数式编程呢?简单来说,它是一种以函数为核心的编程范式,强调以下几个核心理念:
1. 纯函数(Pure Function)
函数的行为只依赖输入参数,不修改外部状态。例如:
javascript
function add(a, b) {
return a + b
}
add(1, 2) // 始终返回 3
2. 不可变性(Immutability)
数据一旦创建就不能被修改,只能通过创建副本来反映变化:
javascript
const arr = [1, 2, 3]
const newArr = [...arr, 4] // 创建新数组而不是修改原数组
3. 声明式编程(Declarative Programming)
关注"做什么"而不是"怎么做"。比如:
javascript
// 命令式写法
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// 声明式写法
arr.forEach(item => console.log(item))
Vue 3 如何体现函数式思想?
- Composable 函数是典型的纯函数,它们接受参数、返回结果,不对外部造成影响;
- Proxy 响应式系统通过代理层隔离副作用,让原始数据始终保持纯净;
- 模板语法鼓励开发者使用声明式的方式描述 UI,而不是手动操作 DOM。
在 Vue 2 的项目中,一个用户管理页面的 data、methods、computed 分散在各处,维护困难。使用 Composition API 重构时,可以将代码按照逻辑功能分组,代码更清晰、可复用性更强。例如:我将所有与'用户表单验证'相关的逻辑(响应式数据、验证函数、规则)抽取为 useUserForm Hook。这使得相同逻辑在不同组件间复用变得清晰 ,代码更像是编写纯函数。在大型项目中,我们通常会按 '功能域'(如useTable、 useAuth) 来组织组合式函数,并与业务组件分离,形成清晰的基础设施层。
如果你喜欢这篇文章,欢迎点赞、收藏或分享给更多朋友。也欢迎在评论区留下你的看法和经验,我们一起交流成长!