趁过年,复习一波vue3,按照github上的教程来实现一个mini-vue3,目前还在实现响应式阶段
初始化目录结构
npm init -y
npm install typescript -D
npx tsc --init
npm install jest @types/jest -D
tsconfig设置配置
"types": ["jest"],
"noImplicitAny": false,
"lib": ["DOM","ES6"],
安装babel
npm install babel-jest @babel/core @babel/preset-env -D
npm install @babel/preset-typescript -D
创建babel.config.js
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
};
响应式实现
实现reactive和effect
测试用例
import { reactive } from "../reactive";
import { effect } from "../effect";
describe("effect", () => {
it("happy path", () => {
const user = reactive({
age: 10,
});
let nextAge: any;
effect(() => {
nextAge = user.age + 1;
});
expect(nextAge).toBe(11);
// update
user.age++;
expect(nextAge).toBe(12);
});
it("should return runner when effect is called",()=>{
let foo = 10
const runner = effect(()=>{
foo++
return 'foo'
})
// effect执行返回runner,调用runner会返回作为effect参数的函数的返回值
expect(foo).toBe(11);
const r = runner()
expect(foo).toBe(12)
expect(r).toBe('foo')
})
});
// reactive.spect.ts
describe("reactive", () => {
it("happy path", () => {
const original = { foo: 1 };
const observed = reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
});
});
实现reactive
// 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;
},
});
}
实现effect、trigger和track
class ReactiveEffect {
private _fn: any;
constructor(fn) {
this._fn = fn;
}
run() {
activeEffect = this;
return 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);
}
// desMap中某个代理对象某个属性所有的依赖
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; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
// 返回fn执行的结果
return _effect.run.bind(_effect);
}
实现effect的scheduler
测试用例
// effect.spec.ts
it("scheduler", () => {
// 1.通过effect的第二个参数 传入一个scheduler
// 2.effect第一次执行的时候 会执行fn
// 3.当响应式对象 更新的时候 不会执行fn 而是会执行scheduler
// 4.当执行effect的返回值runner的时候,会执行fn
let dummy;
let run: any;
const scheduler = jest.fn(() => {
run = runner;
});
const obj = reactive({ foo: 1 });
const runner = effect(
() => {
dummy = obj.foo;
},
{ scheduler }
);
expect(scheduler).not.toHaveBeenCalled()
expect(dummy).toBe(1)
obj.foo++
expect(scheduler).toHaveBeenCalledTimes(1)
expect(dummy).toBe(1)
run()
expect(dummy).toBe(2)
});
代码实现
修改effect接受的参数
// effect.ts
class ReactiveEffect {
private _fn: any;
// scheduler添加public关键字 外部直接通过实例对象.scheduler来获取
constructor(fn,public scheduler?) {
this._fn = fn;
}
run() {
activeEffect = this;
return this._fn();
}
}
let activeEffect; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn,options:any = {}) {
const scheduler = options.scheduler
const _effect = new ReactiveEffect(fn,scheduler);
_effect.run();
return _effect.run.bind(_effect);
}
在trigger函数中判断,是否传入scheduler,当响应式数值更新时,执行传入的scheduler
export function trigger(target, key) {
let depsMap = targetMap.get(target);
let dep = depsMap.get(key);
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
实现effect的stop和onStop
测试用例
// effect.spec.ts
it("stop", () => {
// 1.调用stop方法后,当数值更新,effect里的函数fn不执行,相当于清空依赖
// 2.调用runner方法,当数值更新,effect里的函数fn执行,重新添加依赖
let dummy;
const obj = reactive({ prop: 1 });
const runner = effect(() => {
dummy = obj.prop;
});
obj.prop = 2;
expect(dummy).toBe(2);
stop(runner);
obj.prop = 3;
expect(dummy).toBe(2);
runner();
expect(dummy).toBe(3);
});
代码实现
编写stop方法,这里的runner是effect函数执行返回的runner
// effect.ts
export function stop(runner) {
runner.effect.stop();
}
// effect.ts
let activeEffect: any; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn, options: any = {}) {
const scheduler = options.scheduler;
const _effect = new ReactiveEffect(fn, scheduler);
_effect.run();
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect; // 增加effect属性,保存实例,到时调用实例对象的stop方法清空依赖
return runner;
}
ReactiveEffect上增加stop方法
// effect.ts
class ReactiveEffect {
private _fn: any;
deps = [];
active = true;
public scheduler: Function | undefined;
constructor(fn, scheduler?) {
this._fn = fn;
this.scheduler = scheduler;
}
run() {
activeEffect = this;
return this._fn();
}
stop() {
if (this.active) {
cleanupEffect(this);
this.active = false;
}
}
}
function cleanupEffect(effect) {
effect.deps.forEach((dep: any) => {
dep.delete(effect);
});
effect.deps.length = 0
}
实现effect的onStop方法
测试用例
// effect.spec.ts
it("onStop", () => {
// 传入onStop,当调用stop方法时,传入的onStop会被执行
const obj = reactive({
foo: 1,
});
const onStop = jest.fn();
let dummy;
const runner = effect(
() => {
dummy = obj.foo;
},
{
onStop,
}
);
stop(runner)
expect(onStop).toBeCalledTimes(1)
});
代码实现
修改effect函数
let activeEffect: any; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn, options: any = {}) {
const scheduler = options.scheduler;
const _effect = new ReactiveEffect(fn, scheduler);
_effect.onStop = options.onStop // 增加一个onStop属性
_effect.run();
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
class ReactiveEffect {
private _fn: any;
deps = [];
active = true;
onStop?:()=>void
public scheduler: Function | undefined;
constructor(fn, scheduler?) {
this._fn = fn;
this.scheduler = scheduler;
}
run() {
activeEffect = this;
return this._fn();
}
stop() {
if (this.active) {
cleanupEffect(this);
if(this.onStop){ // 如果传入了onStop方法 调用该方法
this.onStop()
}
this.active = false;
}
}
}
实现readonly
测试用例
// readonly.spec.ts
import { readonly } from "../reactive";
describe('readonly',()=>{
it('happy path',()=>{
const origin = {foo:1,bar:{baz:2}}
const wrapped = readonly(origin)
expect(wrapped).not.toBe(origin)
expect(wrapped.foo).toBe(1)
})
// 更新值的时候回抛出警告
it("warn then call set ",()=>{
console.warn = jest.fn()
const user = readonly({
age:10
})
user.age = 11
expect(console.warn).toBeCalled()
})
})
代码实现
新建basehandler.ts
// basehandler.ts
import { track, trigger } from "./effect";
const get = createGetter()
const set = createSetter()
const readonlyGet = createGetter(true)
function createGetter(isReadonly = false) {
return function get(target, key) {
const res = Reflect.get(target, key);
if (!isReadonly) {
track(target, key);
}
return res;
};
}
function createSetter() {
return function set(target, key, value) {
const res = Reflect.set(target, key, value);
// 触发依赖
trigger(target, key);
return res;
};
}
export const mutableHandlers = {
get,
set
};
export const readonlyHandlers = {
get: readonlyGet,
set(target, key, value) {
// 抛出错误或者警告
console.warn(`key:${key} set 失败,因为target为readonly`)
return true;
},
};
// reactive.ts
import { mutableHandlers, readonlyHandlers } from "./basehandler";
export function reactive(raw) {
return createActiveObject(raw,mutableHandlers)
}
export function readonly(raw) {
return createActiveObject(raw, readonlyHandlers);
}
function createActiveObject(raw, basehandler: any) {
return new Proxy(raw, basehandler);
}
实现isReactive
测试用例
import { isReactive, reactive } from "../reactive";
describe("reactive", () => {
it("happy path", () => {
const original = { foo: 1 };
const observed = reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
// 判断是否是响应式对象
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
});
});
代码实现
// reactive.ts
// 添加枚举类型
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
export function isReactive(value) {
return !!value[ReactiveFlags.IS_REACTIVE];
}
判断一个对象是否是响应式,必然触发get操作,在createGetter中添加判断
// basehandler.ts
import { ReactiveFlags } from "./reactive";
function createGetter(isReadonly = false) {
return function get(target, key) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
}
const res = Reflect.get(target, key);
if (!isReadonly) {
track(target, key);
}
return res;
};
}
实现isReadonly
测试用例
// readonly.spec.ts
it('happy path',()=>{
const origin = {foo:1,bar:{baz:2}}
const wrapped = readonly(origin)
expect(wrapped).not.toBe(origin)
expect(isReadonly(wrapped)).toBe(true)
expect(wrapped.foo).toBe(1)
})
代码实现
// reactive.ts
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly'
}
export function isReadonly(value) {
return !!value[ReactiveFlags.IS_READONLY];
}
function createGetter(isReadonly = false) {
return function get(target, key) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
}
const res = Reflect.get(target, key);
if (!isReadonly) {
track(target, key);
}
return res;
};
}
优化effect的stop功能
测试用例
it("stop", () => {
// 1.调用stop方法后,当数值更新,effect里的函数fn不执行
// 2.调用runner方法,当数值更新,effect里的函数fn执行
let dummy;
const obj = reactive({ prop: 1 });
const runner = effect(() => {
dummy = obj.prop;
});
// 只触发set操作
obj.prop = 2;
expect(dummy).toBe(2);
stop(runner);
obj.prop = 3;
expect(dummy).toBe(2);
// 触发get、set操作 obj.prop = obj.prop + 1
obj.prop++
expect(dummy).toBe(2)
runner();
expect(dummy).toBe(4);
});
代码实现
用一个变量shouldTrack来控制是否收集依赖
// effect.ts
let activeEffect: any;
let shouldTrack; // 是否应该收集依赖
class ReactiveEffect {
private _fn: any;
deps = [];
active = true;
onStop?: () => void;
public scheduler: Function | undefined;
constructor(fn, scheduler?) {
this._fn = fn;
this.scheduler = scheduler;
}
run() {
if (!this.active) {
return this._fn();
}
// 此时应该收集依赖
shouldTrack = true;
activeEffect = this;
// 执行_fn 会触发track
const result = this._fn();
shouldTrack = false;
return result;
}
stop() {
if (this.active) {
cleanupEffect(this);
if (this.onStop) {
this.onStop();
}
this.active = false;
}
}
}
// 收集依赖
const targetMap = new Map();
export function track(target, key) {
if (!activeEffect) return;
// 控制是否收集依赖
if (!shouldTrack) return;
// target(代理的对象) → key → dep
// 代理对象的依赖容器
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// desMap中某个代理对象某个属性所有的依赖
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
if (dep.has(activeEffect)) return;
// 添加依赖
dep.add(activeEffect);
// 反向收集所有依赖到ReactiveEffect实例对象的deps属性上
activeEffect.deps.push(dep);
}
export function effect(fn, options: any = {}) {
const scheduler = options.scheduler;
const _effect = new ReactiveEffect(fn, scheduler);
extend(_effect, options);
_effect.run();
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
嵌套的reactive和readonly对象
测试用例
// reactive.spec.ts
it("nested reactive", () => {
const origin = {
nested: {
foo: 1,
},
array: [{ bar: 2 }],
};
const observed = reactive(origin);
expect(isReactive(origin.nested)).toBe(true);
expect(isReactive(origin.array)).toBe(true);
expect(isReactive(origin.array[0])).toBe(true);
});
// readonly.spec.ts
it('happy path',()=>{
const origin = {foo:1,bar:{baz:2}}
const wrapped = readonly(origin)
expect(wrapped).not.toBe(origin)
expect(isReadonly(wrapped)).toBe(true)
expect(isReadonly(wrapped.bar)).toBe(true)
expect(isReadonly(wrapped.bar)).toBe(true)
expect(isReadonly(origin)).toBe(false)
expect(wrapped.foo).toBe(1)
})
代码实现
// basehandler.ts
function createGetter(isReadonly = false) {
return function get(target, key) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
}
const res = Reflect.get(target, key);
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
if (!isReadonly) {
track(target, key);
}
return res;
};
}
// shared/index.ts
export function isObject(val) {
return val !== null && typeof val === "object";
}