Vue 3响应式原理深入解析:从Proxy到Composition API#
Vue 3的发布带来了许多激动人心的特性,其中最核心的变化之一就是全新的响应式系统。本文将深入探讨Vue 3响应式系统的工作原理,从底层实现到上层应用。
Vue 2 vs Vue 3响应式系统对比#
Vue 2的Object.defineProperty#
Vue 2使用Object.defineProperty来实现响应式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
return value
},
set(newValue) {
// 派发更新
value = newValue
}
})
}
|
限制:
- 无法检测数组索引和长度的变化
- 无法检测对象属性的添加或删除
- 深层嵌套对象需要递归遍历,性能开销大
Vue 3的Proxy#
Vue 3使用ES6的Proxy来重写响应式系统:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
return result
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
// 派发更新
trigger(target, key)
return result
}
})
}
|
优势:
- 可以拦截所有类型的对象操作
- 无需预先遍历所有属性
- 支持数组、Map、Set等集合类型
- 性能更优,按需代理
响应式核心实现#
1. 依赖收集(Dependency Collection)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 全局依赖收集栈
let activeEffect = null
const targetMap = new WeakMap()
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(activeEffect)
}
|
2. 派发更新(Trigger)#
1
2
3
4
5
6
7
8
9
10
11
| function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => {
effect()
})
}
}
|
3. Effect副作用#
1
2
3
4
5
6
7
8
9
10
11
12
13
| function effect(fn, options = {}) {
const effect = () => {
try {
activeEffect = effect
return fn()
} finally {
activeEffect = null
}
}
effect()
return effect
}
|
Ref和Reactive的区别#
ref的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class RefImpl {
constructor(value) {
this._value = value
this._rawValue = value
this._v_isRef = true
}
get value() {
track(this, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = newVal
trigger(this, 'value')
}
}
}
function ref(value) {
return new RefImpl(value)
}
|
reactive的实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function reactive(target) {
if (!isObject(target)) {
return target
}
return createReactiveObject(target)
}
function createReactiveObject(target) {
if (!Object.isExtensible(target)) {
return target
}
const proxy = new Proxy(target, baseHandlers)
return proxy
}
|
Composition API的设计思想#
1. 函数式编程思想#
Composition API采用了函数式编程的思想,将相关的逻辑组织在一起:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // Vue 2 Options API
export default {
data() {
return {
count: 0,
name: 'Vue'
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
}
}
// Vue 3 Composition API
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const increment = () => count.value++
const doubleCount = computed(() => count.value * 2)
return {
count,
name,
increment,
doubleCount
}
}
}
|
2. 更好的逻辑复用#
Composition API让逻辑复用变得更加简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // 自定义Hook
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
increment,
decrement,
reset
}
}
// 在组件中使用
export default {
setup() {
const { count, increment } = useCounter(10)
return {
count,
increment
}
}
}
|
性能优化策略#
1. 懒响应式(Lazy Reactivity)#
1
2
3
4
5
6
7
8
9
| // 只有在访问时才会创建代理
function shallowReactive(target) {
return createReactiveObject(target, true)
}
// 只对第一层属性进行响应式处理
function readonly(target) {
return createReactiveObject(target, false, true)
}
|
2. 批量更新优化#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 将多个更新合并为一个批次
const queue = []
let isFlushing = false
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
}
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
queue.length = 0
isFlushing = false
}
|
实际应用场景#
1. 复杂表单的状态管理#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import { reactive, computed } from 'vue'
export function useFormState(initialState) {
const state = reactive(initialState)
const errors = reactive({})
const validate = () => {
// 验证逻辑
Object.keys(state).forEach(key => {
if (!state[key]) {
errors[key] = `${key} is required`
} else {
delete errors[key]
}
})
return Object.keys(errors).length === 0
}
return {
state,
errors,
validate
}
}
|
2. 数据获取和缓存#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| import { ref, reactive } from 'vue'
export function useApi(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const cache = reactive(new Map())
const fetch = async () => {
if (cache.has(url)) {
data.value = cache.get(url)
return
}
loading.value = true
error.value = null
try {
const response = await fetch(url)
const result = await response.json()
data.value = result
cache.set(url, result)
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
data,
loading,
error,
fetch
}
}
|
Vue 3的响应式系统通过Proxy带来了以下改进:
- 更好的性能:按需代理,避免了不必要的性能开销
- 更强的功能:支持更多数据类型和操作
- 更简洁的API:Composition API提供了更灵活的代码组织方式
- 更好的TypeScript支持:类型推断更加准确
理解Vue 3响应式原理不仅能帮助我们更好地使用Vue,也能启发我们在日常开发中设计出更优秀的响应式系统。