@msom/reaction
Version:
@msom/reaction
312 lines (296 loc) • 8.98 kB
text/typescript
import { assert, equal, getGlobalData, isObject } from "@msom/common";
import {
OPERATORMAPS,
OPERATORTYPES,
TRACKERTYPES,
TRRIGERTYPES,
iterator,
} from "../Operator";
import { $REACTION, IObserver, Reaction } from "../Reaction";
export type ObserverOption<T> = {
initValue?: T;
equal?: (oldValue: T, newValue: T) => boolean;
deep?: boolean;
// TODO: add more options
};
export class Observer<T = unknown> implements IObserver {
private declare destroyed: boolean;
private declare handlers: Set<Reaction>;
private declare value: T;
private declare equal: (oldValue: T, newValue: T) => boolean;
private declare $option: ObserverOption<T>;
constructor(option: ObserverOption<T> = {}) {
this.destroyed = false;
this.equal = option.equal || equal;
this.handlers = new Set();
if (Reflect.has(option, "initValue")) {
const value = option.initValue;
assert(value);
this.value = value;
}
this.$option = option;
}
track() {
const running = getGlobalData("@msom/reaction") as $REACTION;
if (running?.tracking) {
running.tracking(this);
}
}
private reactiveValue(value: T): T {
return this.$option.deep && isObject(value) ? reactive(value) : value;
}
get(): T {
if (this.destroyed) {
return this.value;
}
this.track();
return this.value;
}
set(newValue: T): void {
const { value: oldValue } = this;
// 更新值
this.value = this.reactiveValue(newValue);
// 比较新值
if (!this.destroyed && !this.equal(oldValue, newValue)) {
this.notify();
}
}
notify(): void {
const handles = [...this.handlers];
for (const reaction of handles) {
reaction.notify();
}
}
addReaction(reaction: Reaction): void {
this.handlers.add(reaction);
}
removeReaction(reaction: Reaction): void {
this.handlers.delete(reaction);
}
destroy() {
this.handlers.forEach((reaction) => {
reaction.removeObserver(this);
});
this.destroyed = true;
}
}
function getObserverForce(
target: object,
propKey: PropertyKey,
trackType: TRACKERTYPES
) {
let _target = targetMap.get(target);
if (!_target) {
targetMap.set(target, (_target = { propMap: new Map() }));
}
if (!_target.propMap) {
_target.propMap = new Map();
}
const propMap = _target.propMap;
let typesMap = propMap.get(propKey);
if (!typesMap) {
propMap.set(propKey, (typesMap = new Map()));
}
let observer = typesMap.get(trackType);
if (!observer) {
typesMap.set(
trackType,
(observer = new Observer({ initValue: target[propKey] }))
);
}
return observer;
}
function getObserver(
target: object,
propKey: PropertyKey,
trackType: TRACKERTYPES
) {
return targetMap.get(target)?.propMap?.get(propKey)?.get(trackType);
}
type TypesMap = Map<TRACKERTYPES, Observer>;
type PropMap = Map<PropertyKey, TypesMap>;
type _Target = {
proxy?: unknown;
propMap: PropMap;
};
type TargetMap = WeakMap<WeakKey, _Target>;
const targetMap: TargetMap = new WeakMap();
function track(target: object, propKey: PropertyKey, trackType: TRACKERTYPES) {
getObserverForce(target, propKey, trackType).track();
}
function trriger(
target: object,
propKey: PropertyKey,
trrigerType: TRRIGERTYPES,
destroy?: boolean
) {
const trackTypes = OPERATORMAPS[trrigerType];
for (const trackType of trackTypes) {
const observer = getObserver(target, propKey, trackType);
if (!observer) continue;
observer.notify();
destroy && observer.destroy();
}
}
const reactiveOptions: ProxyHandler<object> = {
get(target, propKey, receiver) {
const value = Reflect.get(target, propKey, receiver);
track(target, propKey, OPERATORTYPES.TRACKER.GET);
if (isObject(value)) {
return reactive(value);
}
if (typeof value === "function") {
return value.bind(receiver);
}
return value;
},
has(target, propKey) {
const result = Reflect.has(target, propKey);
track(target, propKey, OPERATORTYPES.TRACKER.HAS);
return result;
},
ownKeys(target) {
const result = Reflect.ownKeys(target);
// Track iteration for all collection types
track(target, Symbol.iterator, OPERATORTYPES.TRACKER[Symbol.iterator]);
return result;
},
set(target, propKey, value, receiver) {
const result = Reflect.set(target, propKey, value, receiver);
let trrigerType: TRRIGERTYPES = Reflect.has(target, propKey)
? OPERATORTYPES.TRRIGER.SET
: OPERATORTYPES.TRRIGER.ADD;
if (result) {
trriger(target, propKey, trrigerType);
}
return result;
},
deleteProperty(target, propKey) {
const result = Reflect.deleteProperty(target, propKey);
if (result) {
const _target = targetMap.get(target);
if (_target && _target.propMap && _target.propMap.has(propKey)) {
trriger(target, propKey, OPERATORTYPES.TRRIGER.DELETE, true);
_target.propMap.delete(propKey);
}
}
return result;
},
};
function createProxy(target: object) {
if (target instanceof Proxy) {
return target;
}
if (target instanceof Array) {
return new Proxy(target, {
...reactiveOptions,
get(target, propKey, receiver) {
// 收集当前属性的依赖
track(target, propKey, OPERATORTYPES.TRACKER.GET);
const value = Reflect.get(target, propKey, receiver);
if (isObject(value)) {
return reactive(value);
}
if (typeof value === "function") {
const rerwites = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
] as const;
if (rerwites.includes(propKey as (typeof rerwites)[number])) {
return function <K extends (typeof rerwites)[number]>(
this: typeof target,
...args: Parameters<(typeof Array.prototype)[K]>
): ReturnType<(typeof Array.prototype)[K]> {
const result = value.apply(this ?? receiver, args);
trriger(target, Symbol.iterator, OPERATORTYPES.TRRIGER.SET);
return result;
};
}
return value.bind(receiver);
}
return value;
},
});
}
if (target instanceof Set) {
return new Proxy(target, {
...reactiveOptions,
get(target, propKey, receiver) {
// 收集当前属性的依赖
track(target, propKey, OPERATORTYPES.TRACKER.GET);
const value = Reflect.get(target, propKey, receiver);
if (isObject(value)) {
return reactive(value);
}
if (typeof value === "function") {
return value.bind(receiver);
}
return value;
},
});
}
if (target instanceof WeakSet) {
return new Proxy(target, reactiveOptions);
}
if (target instanceof Map) {
return new Proxy(target, {
...reactiveOptions,
get(target, propKey, receiver) {
// 收集当前属性的依赖
track(target, propKey, OPERATORTYPES.TRACKER.GET);
const value = Reflect.get(target, propKey, receiver);
if (isObject(value)) {
return reactive(value);
}
if (typeof value === "function") {
if (propKey === "get") {
return function (this: typeof target, key: PropertyKey) {
const result = value.apply(this ?? receiver, [key]);
track(target, key, OPERATORTYPES.TRACKER.GET);
return result;
};
}
if (propKey === "set") {
return function (
this: typeof target,
key: PropertyKey,
newValue: unknown
) {
const result = value.call(this ?? receiver, key, newValue);
trriger(target, key, OPERATORTYPES.TRRIGER.SET);
return result;
};
}
return function (this: typeof target, ...args: unknown[]) {
const result = value.apply(this ?? receiver, args);
return result;
};
}
return value;
},
});
}
if (target instanceof WeakMap) {
return new Proxy(target, reactiveOptions);
}
return new Proxy(target, reactiveOptions);
}
export function reactive<T extends object>(target: T): T {
let _target = targetMap.get(target) as Required<_Target>;
if (!_target) {
_target = {
propMap: new Map(),
proxy: createProxy(target),
};
targetMap.set(target, _target);
}
if (!_target.proxy) {
_target.proxy = createProxy(target);
}
return _target.proxy as T;
}