跳到主要内容

官方模拟版

2024年03月18日
柏拉文
越努力,越幸运

一、packages/reactivity/src/index.ts


export { effect } from './effect';
export { reactive } from './reactive';
export { ref } from './ref';

二、packages/reactivity/src/ref.ts


import { hashChanged } from '@vue/shared';
import { Dep, createDep } from './dep';
import { activeEffect, trackEffects, triggerEffects } from './effect';
import { toReactive } from './reactive';

export interface Ref<T = any> {
value: T;
}

export function ref(value?: any) {
return createRef(value, false);
}

export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true);
}

function createRef(rawValue: any, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}

export function trackRefValue(ref) {
if (activeEffect) {
trackEffects(ref.dep || (ref.dep = createDep()));
}
}

export function triggerRefValue(ref) {
if (ref.dep) {
triggerEffects(ref.dep);
}
}

class RefImpl<T> {
private _value: T;
private _rawValue: T;
public dep?: Dep = undefined;
public readonly __v_isRef = true;
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = value;
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newValue) {
if (hashChanged(newValue, this._rawValue)) {
this._rawValue = newValue;
this._value = toReactive(newValue);
triggerRefValue(this);
}
}
}

三、packages/reactivity/src/reactive.ts


import { isObject } from '@vue/shared';
import { mutableHandlers } from './baseHandlers';

export const reactiveMap = new WeakMap<WeakMap<object, any>>();

export function reactive(target) {
return createReacttiveObject(target, mutableHandlers, reactiveMap);
}

function createReacttiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>
) {
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(target, baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}

export const toReactive = <T extends unknown>(value: T): T => {
return isObject(value) ? reactive(value as object) : value;
};

四、packages/reactivity/src/effect.ts


import { Dep, createDep } from './dep';

type KeyToDepMap = Map<any, Dep>;
const targetMap = new WeakMap<any, KeyToDepMap>();

export let activeEffect: ReactiveEffect | undefined;

export class ReactiveEffect<T = any> {
fn: () => T;
constructor(fn: () => T) {
this.fn = fn;
}
run() {
activeEffect = this;
return this.fn();
}
}

export function trackEffects(dep: Dep) {
dep.add(activeEffect as ReactiveEffect);
}

export function track(target: object, key: any) {
if (!activeEffect) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
trackEffects(dep);
}

export function triggerEffect(effect: ReactiveEffect) {
effect.run();
}

export function triggerEffects(deps: Dep) {
const effects = Array.isArray(deps) ? deps : [...deps];
for (const effect of effects) {
triggerEffect(effect);
}
}

export function trigger(target: object, key: any, value: any) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const deps: Dep | undefined = depsMap.get(key) as Dep;
if (!deps) {
return;
}
triggerEffects(deps);
}

export function effect<T = any>(fn: () => T) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}

五、packages/reactivity/src/dep.ts


import { ReactiveEffect } from './effect';

export type Dep = Set<ReactiveEffect>;

export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects);
return dep;
};

六、packages/reactivity/src/baseHandlers.ts


import { track, trigger } from './effect';

function createGetter() {
return function get(target: object, key: string | symbol, reaceiver: object) {
const result = Reflect.get(target, key, reaceiver);
track(target, key);
return result;
};
}

function createSetter() {
return function set(
target: object,
key: string | symbol,
value: any,
receiver: object
) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key, value);
return result;
};
}

const get = createGetter();
const set = createSetter();

export const mutableHandlers: ProxyHandler<object> = {
get,
set
};

七、测试用例


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3-Next-Mini Ref 测试用例</title>
<script src="../packages/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<div id="p1"></div>
</div>

<script>
const { ref, effect } = Vue;

const obj = ref({
a: 1
});

effect(() => {
document.querySelector('#p1').innerHTML = obj.value.a;
});

setTimeout(() => {
obj.value.a = 2;
}, 3000);
</script>
</body>
</html>