这篇文章主要讲述vue的响应式原理。
准备工作
为了让typescript支持es6语法,需要将DOM和ES6引入。
打开tsconfig.json,添加如下配置项:
{
"lib": ["DOM", "ES6"]
}
编写reactive代码
创建src/reactivity/reactive.ts文件。
import {track, trigger} from './effect'
export function reactive (raw) {
return new Proxy(raw, {
get (target, key) {
const res = Reflect.get(target, key)
track(target, key)
return res
},
set (target, key, value) {
const res = Reflect.set(target, key, value)
trigger(target, key);
return res
}
})
}
编写测试用例:src/reactivity/tests/reactive.spec.ts。
import {reactive} from '../reactive'
describe("reactive", () => {
it("happy path", () => {
const orgin = {
foo: 1
}
const obversed = reactive(orgin);
expect(obversed).not.toBe(orgin);
expect(obversed.foo).toBe(1);
})
})
编写effect代码
创建src/reactivity/effect.ts文件。
class ReactiveEffect {
private _fn: any
constructor (_fn) {
this._fn = _fn
}
run () {
activeEffect = this;
this._fn()
}
}
const targetMap = new Map()
// 收集依赖
export function track (target, key) {
// 定义依赖
// target -> key -> dep
// const dep = new Set()
let depsMap = targetMap.get(target);
// 如果没有depsMap就创建一个
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep)
}
dep.add(activeEffect);
}
export function trigger (target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}
let activeEffect;
export function effect (fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
编写测试用例:src/reactivity/tests/effect.spec.ts。
import {reactive} from '../reactive'
import {effect} from '../effect'
describe('effect', () => {
it.skip('happy path', () => {
const user = reactive({
age: 10
})
let newAge;
effect(() => {
newAge = user.age + 1;
})
expect(newAge).toBe(11);
user.age ++;
expect(newAge).toBe(12);
})
})