响应式 设计思路
vue2 的响应式处理
在了解 vue3 的响应式之前,需要了解下 vue2 是如何实现响应式的:
vue2 通过Object.defineProperty
API来实现数据的响应式
但是这个API具有以下的缺点:
- 不能监听对象属性新增和删除
- 初始化阶段递归执行
Object.defineProperty
性能负担大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div> <button @click="random">改变 msg 的值</button> <span>{{ msg }}</span> </div> </template>
<script> export default { created() { // 通过生命周期会定义一个数据 this.msg = 'I'm creating at created' }, methods: { random() { this.msg = Math.random() } } } </script>
|
问题:当你执行完上述代码后,你会发现
msg 的值并未发生改变。
解释:
在 created 中定义 this.msg 并不是响应式对象
因为在 vue2 中,只有 data 中定义的数据才具有响应式
使用 vue3 改写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div> <button @click="random">改变 msg 的值</button> <span>{{ msg }}</span> </div> </template>
<script> export default { setup() { const state = reactive({ msg: 'I'm msg' }) const random = () => { state.msg = Math.randmon() } return { random, ...toRef(state) } } } </script>
|
vue3 的响应式处理
通过 Proxy
API 来劫持 target
对象的 getter
和 setter
来实现响应式
1 2
| 因为Proxy劫持的是整个对象,所以它可以检测到任何对 对象 的修改 弥补了 Object.defineProperty 的不足
|
注意:
因为 Proxy
劫持的是当前对象的本身,所以对于对象内部还可能存在的对象,在一开始去实现响应式【触发 get】的时候,它(子对象)并不是响应式的,需要通过判断当前对象属性是否仍是一个对象,如果是一个对象,则需要还通过递归
的方式去重新给子对象设置响应式。
这里用 reactive
的响应式实现代码来演示
具体如何实现 get 和 set 操作的可以查看我的 reactive.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const user = reactive({ age: 10, math: { score: 88 } }) user.math.score ++
...
const res = Reflect.get(target, key)
if (isObject(res)) { return reactive(res) } ...
|
响应式的实现【简易】
实现响应式需要分为几个步骤:
- 创建 effect
- 执行 fn
- 触发 get
- 执行 track
- 把 effect 收集起来作为依赖
我们先用 jest
来编写我们期望的 effect
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| describe('effect', () => { it('happy path', () => { const user = reactive({ age: 10 }) let nextAge effect(() => { nextAge = user.age + 1 }) expect(nextAge).toBe(11)
user.age++ expect(nextAge).toBe(12) }); })
|
[依赖收集]
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
|
@params target: 当前对象 key: 字段 dep: 依赖 ----------第一种情况---------- effect(function effectFn1() => { user.age1 }) effect(function effectFn2() => { user.age1 })
----------第二种情况---------- effect(() => { user.age1 user.age2 })
----------第三种情况---------- effect(function effectFn1() => { user.age1 }) effect(function effectFn2() => { user.age2 })
|
使用TypeScript
来实现功能
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
| function createGetter() { return new Proxy(target, { get(target, key) { const res = Reflect.get(target, key) track(target,key) return res }, set(target, key, value) { Reflect.set(target, key, value) trigger(target, key) } }) }
let activeEffect let bucket = new Map()
function track(target: any, key: any) { let targetMap = bucket.get(target)
if (!targetMap) { bucket.set(target, (targetMap = new Map())) }
let depsMap = targetMap.get(key)
if(!depsMap) { targetMap.set(key, (depsMap = new Set())) } depsMap.add(activeEffect) }
function trigger(target, key) { let targetMap = bucket.get(target)
let depsMap = targetMap.get(key) for(const dep of targetMap[key]) { dep() } }
function effect(fn: any) { activeEffect = fn fn() }
|