vue3响应式核心原理是
reactive,reactive的实现是由Proxy和effect组合
reactive:其定义是接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()
1.reactive单元测试
// reactivity/tests/reactive.spec.ts
import { reactive } from "../reactive" // 后续创建reactive文件后引入
describe('reactive', () => {
it('happy path', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(observed).not.toBe(original)
expect(observed.foo).toBe(1)
})
})
2.实现reactive
- 使用
Proxy进行目标对象的代理 - get时收集依赖
- set时触发依赖
// reactivity/reactive.ts
export function reactive(raw) {
// 将传入的对象用Proxy包裹
return new Proxy(raw, {
get(target, key) {
// 不明白Reflect的同学可以MDN搜索哈
const res = Reflect.get(target, key)
// get时收集依赖
track(target, key)
return res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
// 触发依赖
trigger(target, key)
return res
}
})
}
把收集依赖和触发依赖代码先注释,此时运行yarn test reactive应该可以通过测试
接下来实现effect,实现依赖相关逻辑
3. effect单元测试
// reactivity/tests/effect.spec.ts
import { effect } from "../effect"
import { reactive } from "../reactive"
describe('effect', () => {
it('happy path', () => {
const user = reactive({
age: 10
})
let nextAge
effect(() => {
nextAge = user.age + 1
})
expect(nextAge).toBe(11)
// update
user.age++
expect(nextAge).toBe(12)
})
})
4.简易实现effect
// effect.ts
// 抽离类,根据单测的用法,effect传入fn,且立即调用
class ReactiveEffect {
private _fn: any;
// 传入fn
constructor(fn) {
this._fn = fn
}
// 调用run方法去执行fn时,保存this,保存当前实例
run() {
activeEffect = this
this._fn()
}
}
let targetMap = new Map()// 创建一个Map收集依赖
// 收集依赖,导出track
export function track(target, key) {
// target -> key -> dep
let depsMap = targetMap.get(target)
// 初始化时没有depsMap,建立关系
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
// fn不能重复,因此使用Set,下方触发依赖时调用effect.run()
dep = new Set()
depsMap.set(key, dep)
}
// activeEffect 是当前ReactiveEffect实例对象
// 添加fn
dep.add(activeEffect)
}
// 触发依赖
export function trigger(target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}
let activeEffect;
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
5.完善effect,返回runner
// reactivity/tests/effect.spec.ts
describe('effect', () => {
...// 省略上方测试代码
it('should return runner when call effect', () => {
// effect传入fn->function(runner) 实际上执行的是effect传入fn,fn的返回值 return
// 1. effect(fn)->function(runner)->fn -> fn的返回值 return
let foo = 10
const runner = effect(() => {
foo++
return 'foo'
})
expect(foo).toBe(11)
const r = runner()
expect(foo).toBe(12)
expect(r).toBe('foo')
})
}
6. 实现effect返回runner功能
// effect.ts
// 返回runner,意味着effect将run方法返回,fn有this指向问题,使用bind解决
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
// 返回run方法
return _effect.run.bind(_effect)
}
// ReactiveEffect类中的run
class ReactiveEffect {
private _fn: any;
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
// 返回fn的返回值
return this._fn()
}
}