响应式系统入门:ref 与 reactive 的实现
Vue2 用
Object.defineProperty,Vue3 用Proxy— 为什么?看完这篇就懂了
从一个问题开始
javascript
const data = { name: '张三' }
// 这是响应式吗?
data.name = '李四'
console.log(data.name) // 打印 "李四" — 数据变了,但界面不知道1
2
3
4
5
2
3
4
5
普通对象不是响应式的 — 修改它不会自动通知 Vue 去更新页面。
Vue 的任务就是:让数据变的时候,界面跟着变。
Proxy:更强大的数据劫持
Vue3 使用 Proxy 来"劫持"对象的读写操作。
简单理解 Proxy
javascript
const raw = { count: 0 }
// 创建一个代理:拦截对 raw 的所有操作
const proxy = new Proxy(raw, {
// 读取属性时调用
get(target, key, receiver) {
console.log(`📖 读取 ${key}`)
return Reflect.get(target, key, receiver)
},
// 修改属性时调用
set(target, key, value, receiver) {
console.log(`✏️ 修改 ${key} = ${value}`)
return Reflect.set(target, key, value, receiver)
}
})
// 现在所有操作都被"拦截"了
console.log(proxy.count) // 📖 读取 count
proxy.count = 10 // ✏️ 修改 count = 101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Proxy vs Object.defineProperty
| 特性 | Proxy | defineProperty |
|---|---|---|
| 监听范围 | 整个对象,包括新增属性 | 需要遍历每个属性 |
| 数组友好 | ✅ 原生支持 | ⚠️ 需要 hack |
| 嵌套对象 | ✅ 自动深层代理 | ❌ 需要递归 |
| 删除/遍历 | ✅ 支持 | ❌ 不支持 |
javascript
// defineProperty 的局限
const obj = {}
Object.defineProperty(obj, 'name', {
get() { return '张三' }
})
obj.name // ✅ 正常
obj.age = 18 // ❌ 无法监听新增属性
// Proxy 的优势
const proxy = new Proxy({}, {
get(target, key) { return target[key] },
set(target, key, value) { target[key] = value; return true }
})
proxy.name = '李四' // ✅ 可以监听1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
实现 reactive
reactive 是 Vue3 中用于创建响应式对象的核心 API。
基础实现
javascript
/**
* 创建一个响应式对象
* @param {Object} obj - 原始对象
* @returns {Proxy} 响应式代理对象
*/
function reactive(obj) {
// 使用 Proxy 包装对象
return new Proxy(obj, {
// 拦截属性读取
get(target, key, receiver) {
// TODO: 收集依赖,后面详解
const result = Reflect.get(target, key, receiver)
return result
},
// 拦截属性修改
set(target, key, value, receiver) {
// TODO: 触发更新,后面详解
const result = Reflect.set(target, key, value, receiver)
return result
},
// 拦截属性删除
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
// TODO: 触发更新
return result
},
// 拦截 in 操作符
has(target, key) {
const result = Reflect.has(target, key)
// TODO: 收集依赖
return result
}
})
}
// 测试
const state = reactive({
count: 0,
user: {
name: '张三'
}
})
state.count // 触发 get
state.user.name // 触发 get(深层响应式)
state.count = 10 // 触发 set1
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
40
41
42
43
44
45
46
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
40
41
42
43
44
45
46
深层响应式
javascript
function reactive(obj) {
// 如果是对象,递归处理,保证嵌套对象也是响应式的
if (typeof obj === 'object' && obj !== null) {
return new Proxy(obj, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
// 如果是对象,继续递归代理(懒代理:只在访问时深层代理)
return typeof value === 'object' && value !== null
? reactive(value)
: value
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
}
})
}
// 基础类型直接返回
return obj
}
// 测试深层响应式
const state = reactive({
user: {
profile: {
name: '张三',
age: 25
}
}
})
state.user.profile.name = '李四' // ✅ 深层修改也能触发响应式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
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
实现 ref
ref 用于包装基础类型(字符串、数字、布尔等),让它们变成响应式的。
为什么基础类型需要特殊处理?
javascript
// 基础类型不是对象,Proxy 无法直接代理
const count = 0
// new Proxy(0, {}) ❌ 报错:Target can't be a primitive
// 所以需要用对象包裹一下
const ref = {
value: 0
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
ref 的实现
javascript
/**
* 创建一个响应式引用
* 用于基础类型数据的响应式包装
* @param {*} value - 初始值
* @returns {Object} 带有 value 属性的响应式对象
*/
function ref(value) {
return {
// 实际存储值的地方
get value() {
// TODO: 收集依赖
return value
},
set value(newValue) {
// TODO: 触发更新
if (newValue !== value) {
value = newValue
// TODO: 通知变更
}
}
}
}
// 测试
const count = ref(0)
console.log(count.value) // 0
count.value = 10 // 修改
console.log(count.value) // 101
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
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
更完善的 ref 实现
javascript
/**
* 响应式引用
* @param {*} initialValue - 初始值
*/
function ref(initialValue) {
// 内部存储实际值
let value = initialValue
// 创建一个带有 getter/setter 的对象
const r = {
// 使用 Symbol 确保 key 不会被枚举
[Symbol.iterator]: () => {
let index = 0
return {
next: () => ({
value: index === 0 ? value : undefined,
done: index++ >= 1
})
}
},
// 可读属性
get value() {
// TODO: 依赖收集 track(r, 'value')
return value
},
// 可写属性
set value(newValue) {
// 只有值真正变化时才触发
if (newValue !== value) {
const oldValue = value
value = newValue
// TODO: 触发更新 trigger(r, 'value', newValue, oldValue)
}
}
}
// 标记为 ref 对象(用于类型识别)
Object.defineProperty(r, '__v_isRef', { value: true })
return r
}
// 自动解包:当在 reactive 中访问 ref 时,自动取出 value
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
// 如果是 ref,自动解包
if (value && value.__v_isRef) {
return value.value
}
return typeof value === 'object' ? reactive(value) : value
},
set(target, key, value, receiver) {
// 处理 ref 自动解包后的赋值
const oldValue = target[key]
if (oldValue && oldValue.__v_isRef) {
oldValue.value = value
return true
}
return Reflect.set(target, key, value, receiver)
}
})
}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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
computed 的实现
computed 是计算属性,它会根据依赖自动缓存结果。
核心思路
计算属性 = 依赖追踪 + 缓存结果1
简化实现
javascript
/**
* 创建一个计算属性
* @param {Function} getter - 获取值的函数
* @returns {Object} 计算属性对象
*/
function computed(getter) {
// 缓存上一次的值
let cache = undefined
// 标记是否需要重新计算
let dirty = true
const effect = () => {
// 标记为脏,下次访问需要重新计算
dirty = true
// TODO: 依赖收集
}
const runner = () => {
if (dirty) {
dirty = false
// 执行 getter,重新计算值
cache = getter()
}
return cache
}
return {
// 假装是一个对象,有 value 属性
get value() {
// TODO: 收集当前 effect 依赖
return runner()
}
}
}
// 测试
const count = ref(0)
const doubled = computed(() => {
console.log('🔄 计算中...')
return count.value * 2
})
console.log(doubled.value) // 🔄 计算中... 0
console.log(doubled.value) // (不打印) 使用缓存,返回 0
count.value = 5
console.log(doubled.value) // 🔄 计算中... 10 (count 变了,重新计算)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
40
41
42
43
44
45
46
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
40
41
42
43
44
45
46
完整示例
javascript
// ========== 完整响应式系统示例 ==========
// 依赖图:target -> key -> effects[]
const targetMap = new WeakMap()
/**
* 收集依赖
* 当读取某个属性时,调用此函数记录"谁在用这个属性"
*/
function track(target, key) {
// 获取当前正在执行的 effect
const effect = currentEffect
if (effect) {
// 获取 target 对应的依赖图
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取 key 对应的 effects 列表
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
// 添加当前 effect
deps.add(effect)
console.log(`📡 依赖收集:${target.constructor.name}.${key} <- effect${effect.id}`)
}
}
/**
* 触发更新
* 当修改某个属性时,调用此函数通知所有使用方
*/
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (depsMap) {
const effects = depsMap.get(key)
if (effects) {
effects.forEach(effect => {
console.log(`🚀 触发更新:effect${effect.id}`)
effect.fn()
})
}
}
}
/**
* 创建响应式对象
*/
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key)
const value = Reflect.get(target, key, receiver)
return typeof value === 'object' && value !== null
? reactive(value)
: value
},
set(target, key, value, receiver) {
const oldValue = target[key]
Reflect.set(target, key, value, receiver)
// 只有值真正变了才触发
if (oldValue !== value) {
trigger(target, key)
}
return true
}
})
}
/**
* 创建 ref
*/
function ref(initialValue) {
let value = initialValue
return {
get value() {
track(value, 'value')
return value
},
set value(newValue) {
if (newValue !== value) {
value = newValue
trigger(value, 'value')
}
}
}
}
// ========== 测试 ==========
const count = ref(0)
const doubled = computed(() => count.value * 2)
console.log('\n--- 第一次访问 doubled ---')
console.log('结果:', doubled.value) // 0
console.log('\n--- 修改 count ---')
count.value = 5
console.log('\n--- 第二次访问 doubled ---')
console.log('结果:', doubled.value) // 101
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
真实源码对照
以下代码来自 Vue3 源码(packages/reactivity/src/):
reactive 的实现
typescript
// packages/reactivity/src/reactive.ts
const reactiveMap = new WeakMap();
function createReactiveObject(target) {
if (!isObject(target)) return target;
if (target[ReactiveFlags.IS_REACTIVE]) return target;
// 缓存机制:同一个对象只代理一次
const exitsProxy = reactiveMap.get(target);
if (exitsProxy) return exitsProxy;
const proxy = new Proxy(target, mutableHandlers);
reactiveMap.set(target, proxy);
return proxy;
}
export function reactive(target) {
return createReactiveObject(target);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Proxy Handler 的实现
typescript
// packages/reactivity/src/baseHandler.ts
export const mutableHandlers: ProxyHandler<any> = {
get(target, key, recevier) {
if (key === ReactiveFlags.IS_REACTIVE) return true;
track(target, key); // 依赖收集
let res = Reflect.get(target, key, recevier);
// 深层响应式:取到的对象也进行代理
if (isObject(res)) return reactive(res);
return res;
},
set(target, key, value, recevier) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, recevier);
if (oldValue !== value) {
trigger(target, key, value, oldValue); // 触发更新
}
return result;
}
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
总结
| API | 用途 | 原理 |
|---|---|---|
reactive | 对象响应式 | Proxy 深层代理 |
ref | 基础类型响应式 | 包装对象 + getter/setter |
computed | 计算属性 | 依赖追踪 + 缓存 |
track | 收集依赖 | 建立 target→key→effects 映射 |
trigger | 触发更新 | 从映射中找到 effects 并执行 |