我们可以开始第一个测试的编写了。之前的测试可以删掉。在src\reactivity\tests下创建一个effect.spec.ts测试文件,输入以下内容:
describe('effect', () => {
it.skip('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)
});
})
再创建一个reactive.spec.ts测试文件,编写reactive的简单测试
describe('reactivity', () => {
it('happy path', ()=> {
const original = {foo: 1};
const observed = reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
})
})
接着在src/reactivity下创建reactive.ts文件,简单输入内容
export function reactive(raw){}
并在reactive.spec.ts中进行导入。
import {reactive} from '../reactive'
describe('reactivity', () => {
it('happy path', ()=> {
const original = {foo: 1};
const observed = reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
})
})
这个时候,我们可以进行简单单元测试,运行yarn test reactive,reactive写在test后为会自动匹配文件名。
yarn test reactive
会发现报错了
所以我们接下来是想办法让测试通过。
reactive是通过proxy进行拦截。修改reactive.ts文件内容
export function reactive(raw){
return new Proxy(raw,{
})
}
并修改tsconfig的lib,这样就可以继续编写reactive,没有Proxy报错了。
接着继续编写reafect.ts
export function reactive(raw){
return new Proxy(raw,{
get(target,key){
const res = Reflect.get(target,key);
// TODO依赖收集
return res;
},
set(target,key,value){
const res = Reflect.set(target,key,value);
// TODO 触发依赖
return res;
}
})
}
再次进行单元测试,yarn test reactive
yarn rest reactive
测试通过。
继续返回effect.spec.ts,在里面导入reative
import {reactive } from "../reactive"
describe('effect', () => {
it.skip('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)
});
})
完成后,我们还需要完成effect,新建efect.ts文件。
class ReactiveEffect{
constructor(fn){
this._fn = fn;
}
run(){
this._fn();
}
}
export function effect(fn){
const _effect = new ReactiveEffect(fn);
_effect.run();
}
然后在effect.spec.ts文件中导入effect.
import {effect} from "../effect";
import {reactive } from "../reactive"
describe('effect', () => {
it.skip('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)
});
})
暂时先不管update部分(注释掉),运行测试,是可以测试通过的。接着我们完善update部分,这需要添加依赖收集。我们完善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
let depsMap = targetMap.get(target);
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);
}
let activeEffect;
export function effect(fn){
const _effect = new ReactiveEffect(fn);
_effect.run();
}
然后可以在effect.spec.ts中进行断点调试,调试需要我们安装jest插件。在vscode插件市场中搜索jest即可。
接着,我们需要实现trigger.
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
let depsMap = targetMap.get(target);
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();
}
并修改effect.spec.ts,放开update部分
import {effect} from "../effect";
import {reactive } from "../reactive"
describe('effect', () => {
it.skip('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)
});
})
执行单元测试yarn test,通过。当然,也可以打断点,将整个流程串一下。