mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-24 02:28:12 +00:00
修改rax-simulator-renderer 构建
This commit is contained in:
parent
97acb0742a
commit
d88ab7fe98
@ -1,210 +0,0 @@
|
|||||||
import { IObservable, IDepTreeNode, addObserver, removeObserver } from './observable/observable';
|
|
||||||
import { globalState } from './ global-state';
|
|
||||||
|
|
||||||
export enum DerivationState {
|
|
||||||
// before being run or (outside batch and not being observed)
|
|
||||||
// at this point derivation is not holding any data about dependency tree
|
|
||||||
NOT_TRACKING = -1,
|
|
||||||
// no shallow dependency changed since last computation
|
|
||||||
// won't recalculate derivation
|
|
||||||
UP_TO_DATE = 0,
|
|
||||||
// don't have to recompute on every dependency change, but only when it's needed
|
|
||||||
MYBE_DIRTY = 1,
|
|
||||||
// A shallow dependency has changed since last computation and the derivation
|
|
||||||
// will need to recompute when it's needed next.
|
|
||||||
DIRTY = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDerivation extends IDepTreeNode {
|
|
||||||
observing: IObservable[];
|
|
||||||
dependenciesState: DerivationState;
|
|
||||||
newObserving?: null | IObservable[];
|
|
||||||
runId?: number; // Id of the current run of a derivation.
|
|
||||||
unboundDepsCount?: number; // amount of dependencies used by the derivation in this run, which has not been bound yet.
|
|
||||||
onBecomeDirty(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CaughtException {
|
|
||||||
constructor(public cause: any) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCaughtException(e: any): e is CaughtException {
|
|
||||||
return e instanceof CaughtException;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModifiedValue {
|
|
||||||
ifModified(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isModifiedValue(v: any): v is ModifiedValue {
|
|
||||||
return v.ifModified ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldCompute(derivation: IDerivation): boolean {
|
|
||||||
switch (derivation.dependenciesState) {
|
|
||||||
case DerivationState.UP_TO_DATE:
|
|
||||||
return false;
|
|
||||||
case DerivationState.NOT_TRACKING:
|
|
||||||
case DerivationState.DIRTY:
|
|
||||||
return true;
|
|
||||||
case DerivationState.MYBE_DIRTY: {
|
|
||||||
const prevUntracked = untrackedStart();
|
|
||||||
const obs = derivation.observing;
|
|
||||||
const l = obs.length;
|
|
||||||
for (let i = 0; i < l; i++) {
|
|
||||||
const obj = obs[i];
|
|
||||||
if (isModifiedValue(obj)) {
|
|
||||||
obj.ifModified();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((derivation.dependenciesState as any) === DerivationState.DIRTY) {
|
|
||||||
untrackedEnd(prevUntracked);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changeDependenciesStateTo0(derivation);
|
|
||||||
untrackedEnd(prevUntracked);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runDerivedFunction(derivation: IDerivation, f: (...args: any[]) => any, context?: any) {
|
|
||||||
const prevTracking = globalState.trackingDerivation;
|
|
||||||
// pre allocate array allocation + room for variation in deps
|
|
||||||
derivation.newObserving = new Array(derivation.observing.length + 100);
|
|
||||||
derivation.unboundDepsCount = 0;
|
|
||||||
derivation.runId = ++globalState.runId;
|
|
||||||
globalState.trackingDerivation = derivation;
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = f.call(context);
|
|
||||||
} catch (e) {
|
|
||||||
result = new CaughtException(e);
|
|
||||||
}
|
|
||||||
globalState.trackingDerivation = prevTracking;
|
|
||||||
changeDependenciesStateTo0(derivation);
|
|
||||||
bindDependencies(derivation);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindDependencies(derivation: IDerivation) {
|
|
||||||
const prevObserving = derivation.observing;
|
|
||||||
const observing = (derivation.observing = derivation.newObserving!);
|
|
||||||
let lowestNewObservingDerivationState = DerivationState.UP_TO_DATE;
|
|
||||||
|
|
||||||
// Go through all new observables and check diffValue: (this list can contain duplicates):
|
|
||||||
// 0: first occurrence, change to 1 and keep it
|
|
||||||
// 1: extra occurrence, drop it
|
|
||||||
let i0 = 0;
|
|
||||||
let l = derivation.unboundDepsCount!;
|
|
||||||
for (let i = 0; i < l; i++) {
|
|
||||||
const dep = observing[i];
|
|
||||||
if (!dep.diffFlag) {
|
|
||||||
dep.diffFlag = true;
|
|
||||||
if (i0 !== i) {
|
|
||||||
observing[i0] = dep;
|
|
||||||
}
|
|
||||||
i0++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
|
|
||||||
// not hitting the condition
|
|
||||||
if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
|
|
||||||
lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observing.length = i0;
|
|
||||||
|
|
||||||
derivation.newObserving = null;
|
|
||||||
// Go through all old observables and check diffValue: (it is unique after last bindDependencies)
|
|
||||||
// 0: it's not in new observables, unobserve it
|
|
||||||
// 1: it keeps being observed, don't want to notify it. change to 0
|
|
||||||
l = prevObserving.length;
|
|
||||||
while (l--) {
|
|
||||||
const dep = prevObserving[l];
|
|
||||||
if (!dep.diffFlag) {
|
|
||||||
removeObserver(dep, derivation);
|
|
||||||
}
|
|
||||||
dep.diffFlag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through all new observables and check diffValue: (now it should be unique)
|
|
||||||
// 0: it was set to 0 in last loop. don't need to do anything.
|
|
||||||
// 1: it wasn't observed, let's observe it. set back to 0
|
|
||||||
while (i0--) {
|
|
||||||
const dep = observing[i0];
|
|
||||||
if (dep.diffFlag) {
|
|
||||||
dep.diffFlag = false;
|
|
||||||
addObserver(dep, derivation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some new observed derivations may become stale during this derivation computation
|
|
||||||
// so they have had no chance to propagate staleness (#916)
|
|
||||||
if (lowestNewObservingDerivationState !== DerivationState.UP_TO_DATE) {
|
|
||||||
derivation.dependenciesState = lowestNewObservingDerivationState;
|
|
||||||
derivation.onBecomeDirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearObserving(derivation: IDerivation) {
|
|
||||||
const obs = derivation.observing;
|
|
||||||
derivation.observing = [];
|
|
||||||
let i = obs.length;
|
|
||||||
while (i--) {
|
|
||||||
removeObserver(obs[i], derivation);
|
|
||||||
}
|
|
||||||
|
|
||||||
derivation.dependenciesState = DerivationState.NOT_TRACKING;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function untracked<T>(action: () => T): T {
|
|
||||||
const prev = untrackedStart();
|
|
||||||
const res = action();
|
|
||||||
untrackedEnd(prev);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function untrackedStart(): IDerivation | null {
|
|
||||||
const prev = globalState.trackingDerivation;
|
|
||||||
globalState.trackingDerivation = null;
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function untrackedEnd(prev: IDerivation | null) {
|
|
||||||
globalState.trackingDerivation = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function changeDependenciesStateTo0(derivation: IDerivation) {
|
|
||||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
derivation.dependenciesState = DerivationState.UP_TO_DATE;
|
|
||||||
|
|
||||||
const obs = derivation.observing;
|
|
||||||
let i = obs.length;
|
|
||||||
while (i--) {
|
|
||||||
obs[i].lowestObserverState = DerivationState.UP_TO_DATE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDerivationDirty(derivation: IDerivation) {
|
|
||||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
|
||||||
derivation.onBecomeDirty();
|
|
||||||
}
|
|
||||||
derivation.dependenciesState = DerivationState.DIRTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDerivationMybeDirty(derivation: IDerivation) {
|
|
||||||
if (derivation.dependenciesState === DerivationState.UP_TO_DATE) {
|
|
||||||
derivation.onBecomeDirty();
|
|
||||||
}
|
|
||||||
derivation.dependenciesState = DerivationState.MYBE_DIRTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetDerivationState(derivation: IDerivation) {
|
|
||||||
derivation.dependenciesState = DerivationState.NOT_TRACKING;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { IDerivation } from './derivation';
|
|
||||||
import { Reaction } from './reaction';
|
|
||||||
import { IObservable } from './observable/observable';
|
|
||||||
|
|
||||||
export class Globals {
|
|
||||||
/**
|
|
||||||
* Currently running derivation
|
|
||||||
*/
|
|
||||||
trackingDerivation: IDerivation | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are we running a computation currently? (not a reaction)
|
|
||||||
*/
|
|
||||||
computationDepth = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each time a derivation is tracked, it is assigned a unique run-id
|
|
||||||
*/
|
|
||||||
runId = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 'guid' for general purpose. Will be persisted amongst resets.
|
|
||||||
*/
|
|
||||||
guid = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are we in a batch block? (and how many of them)
|
|
||||||
*/
|
|
||||||
inBatch = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observables that don't have observers anymore
|
|
||||||
*/
|
|
||||||
pendingUnobservations: IObservable[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of scheduled, not yet executed, reactions.
|
|
||||||
*/
|
|
||||||
pendingReactions: Reaction[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are we currently processing reactions?
|
|
||||||
*/
|
|
||||||
isRunningReactions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* disable dynamic observe
|
|
||||||
*/
|
|
||||||
dynamicObserveDisabled = false;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.trackingDerivation = null;
|
|
||||||
this.computationDepth = 0;
|
|
||||||
this.runId = 0;
|
|
||||||
this.guid = 0;
|
|
||||||
this.inBatch = 0;
|
|
||||||
this.pendingUnobservations = [];
|
|
||||||
this.pendingReactions = [];
|
|
||||||
this.isRunningReactions = false;
|
|
||||||
this.dynamicObserveDisabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalState: Globals = new Globals();
|
|
||||||
|
|
||||||
export function getGlobalState(): Globals {
|
|
||||||
return globalState;
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
const callbacks: Array<() => void> = [];
|
|
||||||
let pending = false;
|
|
||||||
|
|
||||||
function flush() {
|
|
||||||
pending = false;
|
|
||||||
const copies = callbacks.slice(0);
|
|
||||||
callbacks.length = 0;
|
|
||||||
for (const fn of copies) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let timerFlush: () => void;
|
|
||||||
if (typeof process === 'object' && process.nextTick) {
|
|
||||||
timerFlush = () => process.nextTick(flush);
|
|
||||||
} else if (typeof Promise === 'function') {
|
|
||||||
// tslint:disable-line
|
|
||||||
const timer = Promise.resolve(); // tslint:disable-line
|
|
||||||
timerFlush = () => {
|
|
||||||
timer.then(flush);
|
|
||||||
// if (isIOS) setTimeout(noop)
|
|
||||||
};
|
|
||||||
} else if (typeof MessageChannel === 'function') {
|
|
||||||
const channel = new MessageChannel();
|
|
||||||
const port = channel.port2;
|
|
||||||
channel.port1.onmessage = flush;
|
|
||||||
timerFlush = () => {
|
|
||||||
port.postMessage(1);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
timerFlush = () => {
|
|
||||||
setTimeout(flush, 0);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearTicks() {
|
|
||||||
callbacks.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nextTick(callback?: () => void): Promise<any> {
|
|
||||||
let _resovle: () => void;
|
|
||||||
|
|
||||||
callbacks.push(() => {
|
|
||||||
callback && callback();
|
|
||||||
_resovle();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pending) {
|
|
||||||
pending = true;
|
|
||||||
timerFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
_resovle = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { getProxiedValue } from './proxy';
|
|
||||||
|
|
||||||
export function is(a: any, b: any) {
|
|
||||||
return getProxiedValue(a) === getProxiedValue(b);
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import { isObject } from 'lodash/isObject';
|
|
||||||
import { nextId } from '../utils';
|
|
||||||
import { DerivationState, IDerivation, setDerivationDirty } from '../derivation';
|
|
||||||
import { globalState } from '../ global-state';
|
|
||||||
import Obx, { hasObx, getObx, injectObx, ObxFlag } from './obx';
|
|
||||||
|
|
||||||
export interface IDepTreeNode {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
observing: IObservable[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IObservable extends IDepTreeNode {
|
|
||||||
diffFlag?: boolean;
|
|
||||||
|
|
||||||
observers: Set<IDerivation>;
|
|
||||||
|
|
||||||
// Used to avoid redundant propagations
|
|
||||||
lowestObserverState: DerivationState;
|
|
||||||
// Used to push itself to global.pendingUnobservations at most once per batch.
|
|
||||||
isPendingUnobservation?: boolean;
|
|
||||||
|
|
||||||
// Id of the derivation *run* that last accessed this observable.
|
|
||||||
lastAccessedBy?: number;
|
|
||||||
isBeingObserved?: boolean;
|
|
||||||
onBecomeUnobserved(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addObserver(observable: IObservable, node: IDerivation) {
|
|
||||||
observable.observers.add(node);
|
|
||||||
|
|
||||||
if (observable.lowestObserverState > node.dependenciesState) {
|
|
||||||
observable.lowestObserverState = node.dependenciesState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeObserver(observable: IObservable, node: IDerivation) {
|
|
||||||
observable.observers.delete(node);
|
|
||||||
if (observable.observers.size === 0) {
|
|
||||||
// deleting last observer
|
|
||||||
queueForUnobservation(observable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queueForUnobservation(observable: IObservable) {
|
|
||||||
if (!observable.isPendingUnobservation) {
|
|
||||||
observable.isPendingUnobservation = true;
|
|
||||||
globalState.pendingUnobservations.push(observable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startBatch() {
|
|
||||||
globalState.inBatch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function endBatch() {
|
|
||||||
if (--globalState.inBatch === 0) {
|
|
||||||
// the batch is actually about to finish, all unobserving should happen here.
|
|
||||||
const list = globalState.pendingUnobservations;
|
|
||||||
for (let i = 0; i < list.length; i++) {
|
|
||||||
const observable = list[i];
|
|
||||||
observable.isPendingUnobservation = false;
|
|
||||||
if (observable.observers.size === 0) {
|
|
||||||
if (observable.isBeingObserved) {
|
|
||||||
// if this observable had reactive observers, trigger the hooks
|
|
||||||
observable.isBeingObserved = false;
|
|
||||||
observable.onBecomeUnobserved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
globalState.pendingUnobservations = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reportObserved(observable: IObservable): void {
|
|
||||||
const derivation = globalState.trackingDerivation;
|
|
||||||
if (!derivation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (derivation.runId !== observable.lastAccessedBy) {
|
|
||||||
observable.lastAccessedBy = derivation.runId;
|
|
||||||
derivation.newObserving![derivation.unboundDepsCount!++] = observable;
|
|
||||||
if (!observable.isBeingObserved) {
|
|
||||||
observable.isBeingObserved = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function propagateChanged(observable: IObservable, force = false) {
|
|
||||||
if (observable.lowestObserverState === DerivationState.DIRTY && !force) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
observable.lowestObserverState = DerivationState.DIRTY;
|
|
||||||
observable.observers.forEach((d) => setDerivationDirty(d));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function propagateChangeConfirmed(observable: IObservable) {
|
|
||||||
if (observable.lowestObserverState === DerivationState.DIRTY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
observable.lowestObserverState = DerivationState.DIRTY;
|
|
||||||
|
|
||||||
observable.observers.forEach((d) => {
|
|
||||||
if (d.dependenciesState === DerivationState.MYBE_DIRTY) {
|
|
||||||
d.dependenciesState = DerivationState.DIRTY;
|
|
||||||
} else if (d.dependenciesState === DerivationState.UP_TO_DATE) {
|
|
||||||
observable.lowestObserverState = DerivationState.UP_TO_DATE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function propagateMaybeChanged(observable: IObservable) {
|
|
||||||
if (observable.lowestObserverState !== DerivationState.UP_TO_DATE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
observable.lowestObserverState = DerivationState.MYBE_DIRTY;
|
|
||||||
|
|
||||||
observable.observers.forEach((d) => {
|
|
||||||
if (d.dependenciesState === DerivationState.UP_TO_DATE) {
|
|
||||||
d.dependenciesState = DerivationState.MYBE_DIRTY;
|
|
||||||
d.onBecomeDirty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asObservable(thing: any, obxFlag: ObxFlag): Obx | undefined {
|
|
||||||
if (!isObject(thing)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasObx(thing)) {
|
|
||||||
return getObx(thing);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Object.isExtensible(thing)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = (thing.constructor.name || 'ObservableObject') + '@' + nextId();
|
|
||||||
const ObxContructor = (asObservable as any).getObxContructor(thing);
|
|
||||||
const obx = ObxContructor ? new ObxContructor(name, thing, obxFlag) : null;
|
|
||||||
|
|
||||||
if (obx) {
|
|
||||||
injectObx(thing, obx);
|
|
||||||
return obx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(asObservable as any).getObxContructor = () => Obx;
|
|
||||||
|
|
||||||
export function observeIterable(items: Iterable<any>, obxFlag: ObxFlag): void {
|
|
||||||
for (const n of items) {
|
|
||||||
asObservable(n, obxFlag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reportPropValue(propValue: any, propFlag: ObxFlag): void {
|
|
||||||
if (propValue == null) return;
|
|
||||||
|
|
||||||
const x = propFlag > ObxFlag.REF ? asObservable(propValue, propFlag) : getObx(propValue);
|
|
||||||
|
|
||||||
if (x) {
|
|
||||||
reportObserved(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reportChildValue(propValue: any, ownerFlag: ObxFlag): void {
|
|
||||||
if (propValue == null) return;
|
|
||||||
|
|
||||||
const x =
|
|
||||||
ownerFlag > ObxFlag.VAL
|
|
||||||
? asObservable(propValue, ownerFlag === ObxFlag.DEEP ? ObxFlag.DEEP : ObxFlag.VAL)
|
|
||||||
: getObx(propValue);
|
|
||||||
|
|
||||||
if (x) {
|
|
||||||
reportObserved(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
import { addHiddenProp } from '../utils';
|
|
||||||
import { observeIterable, reportChildValue } from './observable';
|
|
||||||
import { supportProxy, isProxied, createProxy, SYMBOL_PROXY, SYMBOL_RAW, getProxiedValue, getRawValue } from './proxy';
|
|
||||||
import Obx, { getObx, SYMBOL_OBX, ObxFlag } from './obx';
|
|
||||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
|
||||||
|
|
||||||
export function childFlag(flag: ObxFlag) {
|
|
||||||
return flag === ObxFlag.DEEP ? ObxFlag.DEEP : ObxFlag.VAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidArrayIndex(val: any, limit: number = -1): boolean {
|
|
||||||
const n = parseFloat(String(val));
|
|
||||||
return n >= 0 && Math.floor(n) === n && isFinite(val) && (limit < 0 || n < limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ObxArray extends Obx<any[]> {
|
|
||||||
constructor(name: string, target: any[], obxFlag: ObxFlag = ObxFlag.DEEP) {
|
|
||||||
super(name, target, obxFlag);
|
|
||||||
|
|
||||||
if (supportProxy) {
|
|
||||||
this.target = createProxy(target, arrayProxyTraps);
|
|
||||||
} else if (obxFlag > ObxFlag.VAL) {
|
|
||||||
observeIterable(target, childFlag(obxFlag));
|
|
||||||
}
|
|
||||||
setPrototypeOf(target, arrayMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: PropertyKey, val: any) {
|
|
||||||
const target = this.target;
|
|
||||||
if (isValidArrayIndex(key)) {
|
|
||||||
const index = Number(key);
|
|
||||||
target.length = Math.max(target.length, index);
|
|
||||||
target.splice(index, 1, val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.set(key, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
del(key: PropertyKey) {
|
|
||||||
if (isValidArrayIndex(key, this.target.length)) {
|
|
||||||
this.target.splice(Number(key), 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.del(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======= patches ========
|
|
||||||
const arrayProto = Array.prototype;
|
|
||||||
const arrayMethods = Object.create(arrayProto);
|
|
||||||
|
|
||||||
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
|
|
||||||
const original = (arrayProto as any)[method];
|
|
||||||
addHiddenProp(arrayMethods, method, function mutator(this: any[], ...args: any[]) {
|
|
||||||
const obx = getObx(this) as ObxArray;
|
|
||||||
const proxied = isProxied(this);
|
|
||||||
const length = this.length;
|
|
||||||
// apply to rawTarget avoid to call Proxy.set
|
|
||||||
const result = original.apply(getRawValue(this), args);
|
|
||||||
|
|
||||||
let changed = true;
|
|
||||||
let inserted;
|
|
||||||
switch (method) {
|
|
||||||
case 'push':
|
|
||||||
case 'unshift':
|
|
||||||
inserted = args;
|
|
||||||
changed = inserted.length > 0;
|
|
||||||
break;
|
|
||||||
case 'splice':
|
|
||||||
inserted = args.slice(2);
|
|
||||||
changed = inserted.length > 0 || this.length !== length;
|
|
||||||
break;
|
|
||||||
case 'pop':
|
|
||||||
case 'shift':
|
|
||||||
changed = this.length !== length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!proxied && obx.obxFlag > ObxFlag.VAL) {
|
|
||||||
if (inserted && inserted.length > 0) {
|
|
||||||
observeIterable(inserted, childFlag(obx.obxFlag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obx && changed) {
|
|
||||||
obx.reportChange();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const arrayProxyTraps: ProxyHandler<any[]> = {
|
|
||||||
has(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_RAW) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return name in rawTarget;
|
|
||||||
},
|
|
||||||
get(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_RAW) {
|
|
||||||
return rawTarget;
|
|
||||||
}
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
|
||||||
return (rawTarget as any)[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidArrayIndex(name)) {
|
|
||||||
const v = rawTarget[Number(name)];
|
|
||||||
const obx = getObx(rawTarget);
|
|
||||||
if (obx) {
|
|
||||||
reportChildValue(v, obx.obxFlag);
|
|
||||||
}
|
|
||||||
return getProxiedValue(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getProxiedValue((rawTarget as any)[name]);
|
|
||||||
},
|
|
||||||
set(rawTarget, name: PropertyKey, value: any) {
|
|
||||||
if (name === 'length') {
|
|
||||||
rawTarget[name] = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidArrayIndex(name)) {
|
|
||||||
const index = Number(name);
|
|
||||||
rawTarget.length = Math.max(rawTarget.length, index);
|
|
||||||
rawTarget.splice(index, 1, value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
(rawTarget as any)[name] = value;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
deleteProperty(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in arrayMethods) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidArrayIndex(name)) {
|
|
||||||
rawTarget.splice(Number(name), 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete (rawTarget as any)[name];
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
preventExtensions() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { DecoratorTarget } from '../decorators';
|
|
||||||
import Obx from './obx';
|
|
||||||
|
|
||||||
export default class ObxInstance extends Obx<DecoratorTarget> {
|
|
||||||
set(key: PropertyKey, val: any) {
|
|
||||||
const target = this.target;
|
|
||||||
if (key in target) {
|
|
||||||
(target as any)[key] = val;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.set(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import Obx, { ObxFlag } from './obx';
|
|
||||||
import { patchMutator, patchAccessor } from './obx-set';
|
|
||||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
|
||||||
|
|
||||||
type MapType = Map<PropertyKey, any>;
|
|
||||||
|
|
||||||
export default class ObxMap extends Obx<MapType> {
|
|
||||||
constructor(name: string, target: MapType, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
|
||||||
super(name, target, obxFlag);
|
|
||||||
|
|
||||||
setPrototypeOf(target, mapMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: PropertyKey) {
|
|
||||||
return this.target.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: PropertyKey, val: any) {
|
|
||||||
this.target.set(key, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: PropertyKey) {
|
|
||||||
return this.target.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
del(key: PropertyKey) {
|
|
||||||
this.target.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======= Map ========
|
|
||||||
const mapProto = Map.prototype;
|
|
||||||
const mapMethods = Object.create(mapProto);
|
|
||||||
|
|
||||||
patchMutator(['set', 'clear', 'delete'], mapProto, mapMethods);
|
|
||||||
|
|
||||||
patchAccessor(['values', 'entries', Symbol.iterator, 'forEach', 'get'], mapProto, mapMethods);
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
import { walk } from '../utils';
|
|
||||||
import { supportProxy, createProxy, getProxiedValue, SYMBOL_PROXY, SYMBOL_RAW } from './proxy';
|
|
||||||
import Obx, { getObx, SYMBOL_OBX, ObxFlag } from './obx';
|
|
||||||
import { defineObxProperty, ensureObxProperty } from './obx-property';
|
|
||||||
import { hasOwnProperty } from '../../utils/has-own-property';
|
|
||||||
|
|
||||||
function propFlag(flag: ObxFlag) {
|
|
||||||
return flag === ObxFlag.DEEP ? ObxFlag.DEEP : flag - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ObxObject extends Obx<object> {
|
|
||||||
constructor(name: string, target: object, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
|
||||||
super(name, target, obxFlag);
|
|
||||||
|
|
||||||
if (supportProxy) {
|
|
||||||
this.target = createProxy(target, objectProxyTraps);
|
|
||||||
} else if (obxFlag > ObxFlag.REF) {
|
|
||||||
walk(target as any, (obj, key, val) => {
|
|
||||||
defineObxProperty(obj, key, val, undefined, propFlag(obxFlag));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: PropertyKey, val: any) {
|
|
||||||
const target = this.target;
|
|
||||||
if (key in target && !(key in objectProto)) {
|
|
||||||
(target as any)[key] = val;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.set(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectProto = Object.prototype;
|
|
||||||
|
|
||||||
const objectProxyTraps: ProxyHandler<any> = {
|
|
||||||
has(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_RAW) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return name in rawTarget;
|
|
||||||
},
|
|
||||||
get(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_RAW) {
|
|
||||||
return rawTarget;
|
|
||||||
}
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in objectProto) {
|
|
||||||
return rawTarget[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasOwnProperty(rawTarget, name)) {
|
|
||||||
const obx = getObx(rawTarget);
|
|
||||||
if (obx) {
|
|
||||||
ensureObxProperty(rawTarget, name, propFlag(obx.obxFlag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getProxiedValue(rawTarget[name]);
|
|
||||||
},
|
|
||||||
set(rawTarget, name: PropertyKey, value: any) {
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || name in objectProto) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasOwnProperty(rawTarget, name)) {
|
|
||||||
const obx = getObx(rawTarget);
|
|
||||||
if (obx) {
|
|
||||||
defineObxProperty(rawTarget, name, value, undefined, propFlag(obx.obxFlag));
|
|
||||||
obx.reportChange();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rawTarget[name] = value;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
deleteProperty(rawTarget, name: PropertyKey) {
|
|
||||||
if (name === SYMBOL_OBX || name === SYMBOL_PROXY || !hasOwnProperty(rawTarget, name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete rawTarget[name];
|
|
||||||
const obx = getObx(rawTarget);
|
|
||||||
if (obx) {
|
|
||||||
obx.reportChange();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
preventExtensions() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
import { globalState } from '../ global-state';
|
|
||||||
import {
|
|
||||||
untrackedStart,
|
|
||||||
untrackedEnd,
|
|
||||||
IDerivation,
|
|
||||||
DerivationState,
|
|
||||||
runDerivedFunction,
|
|
||||||
shouldCompute,
|
|
||||||
isCaughtException,
|
|
||||||
clearObserving,
|
|
||||||
setDerivationDirty,
|
|
||||||
} from '../derivation';
|
|
||||||
import {
|
|
||||||
IObservable,
|
|
||||||
reportObserved,
|
|
||||||
startBatch,
|
|
||||||
endBatch,
|
|
||||||
propagateChangeConfirmed,
|
|
||||||
propagateMaybeChanged,
|
|
||||||
reportPropValue,
|
|
||||||
} from './observable';
|
|
||||||
import { nextId } from '../utils';
|
|
||||||
import { ObxFlag, SYMBOL_OBX, getObx } from './obx';
|
|
||||||
import { getProxiedValue } from './proxy';
|
|
||||||
import { is } from './compare';
|
|
||||||
import { isPrimitive } from '../utils/is-primitive';
|
|
||||||
import { invariant } from '../utils/invariant';
|
|
||||||
import { hasOwnProperty } from '../utils/has-own-property';
|
|
||||||
|
|
||||||
function getVer(obj: any): number {
|
|
||||||
const obx = getObx(obj);
|
|
||||||
return obx ? obx.localVer : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asNewValue(obj: object) {
|
|
||||||
const obx = getObx(obj);
|
|
||||||
if (obx) {
|
|
||||||
obx.localVer = obx.localVer + 1;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DIRTY = Symbol('dirty');
|
|
||||||
|
|
||||||
export default class ObxProperty implements IObservable, IDerivation {
|
|
||||||
id = nextId();
|
|
||||||
observing: IObservable[] = [];
|
|
||||||
observers = new Set<IDerivation>();
|
|
||||||
dependenciesState = DerivationState.NOT_TRACKING;
|
|
||||||
lowestObserverState = DerivationState.UP_TO_DATE;
|
|
||||||
|
|
||||||
private isComputing = false;
|
|
||||||
private isRunningSetter = false;
|
|
||||||
private pending = false;
|
|
||||||
private pendingValue: any = null;
|
|
||||||
private objectVer = 0;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public name: string,
|
|
||||||
public scope: object | null,
|
|
||||||
private getter?: (...rest: any[]) => any,
|
|
||||||
private setter?: (v: any) => void,
|
|
||||||
private value?: any,
|
|
||||||
private obxFlag: ObxFlag = ObxFlag.DEEP,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
onBecomeDirty() {
|
|
||||||
propagateMaybeChanged(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onBecomeUnobserved() {
|
|
||||||
clearObserving(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ifModified() {
|
|
||||||
if (this.getter && shouldCompute(this)) {
|
|
||||||
startBatch();
|
|
||||||
if (this.computeValue()) {
|
|
||||||
propagateChangeConfirmed(this);
|
|
||||||
this.objectVer = getVer(this.value);
|
|
||||||
}
|
|
||||||
endBatch();
|
|
||||||
} else if (this.pending) {
|
|
||||||
this.pending = false;
|
|
||||||
const oldValue = this.value;
|
|
||||||
this.value = this.pendingValue;
|
|
||||||
if (!this.is(oldValue, this.value)) {
|
|
||||||
propagateChangeConfirmed(this);
|
|
||||||
this.objectVer = getVer(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private is(oldValue: any, value: any) {
|
|
||||||
return oldValue !== DIRTY && is(oldValue, value) && (isPrimitive(value) || getVer(value) === this.objectVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
|
||||||
invariant(!this.isComputing, `Cycle detected in computation ${this.name}`, this.getter);
|
|
||||||
|
|
||||||
reportObserved(this);
|
|
||||||
|
|
||||||
this.ifModified();
|
|
||||||
const result = this.value!;
|
|
||||||
|
|
||||||
if (isCaughtException(result)) {
|
|
||||||
throw result.cause;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportPropValue(result, this.obxFlag);
|
|
||||||
|
|
||||||
return getProxiedValue(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(value: any) {
|
|
||||||
invariant(!this.isRunningSetter, `The setter of observable value '${this.name}' is trying to update itself.`);
|
|
||||||
|
|
||||||
invariant(Boolean(this.setter || !this.getter), `Cannot assign a new value to readonly value '${this.name}'.`);
|
|
||||||
|
|
||||||
const oldValue = this.pending ? this.pendingValue : this.value;
|
|
||||||
|
|
||||||
if (!isCaughtException(oldValue) && this.is(oldValue, value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.setter) {
|
|
||||||
this.pendingValue = value;
|
|
||||||
if (!this.pending) {
|
|
||||||
this.pending = true;
|
|
||||||
propagateMaybeChanged(this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.isRunningSetter = true;
|
|
||||||
const prevTracking = untrackedStart();
|
|
||||||
try {
|
|
||||||
this.value = DIRTY;
|
|
||||||
this.setter!.call(this.scope, value);
|
|
||||||
} finally {
|
|
||||||
untrackedEnd(prevTracking);
|
|
||||||
}
|
|
||||||
this.isRunningSetter = false;
|
|
||||||
setDerivationDirty(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private computeValue(): boolean {
|
|
||||||
const oldValue = this.value;
|
|
||||||
this.isComputing = true;
|
|
||||||
globalState.computationDepth++;
|
|
||||||
this.value = runDerivedFunction(this, this.getter!, this.scope);
|
|
||||||
globalState.computationDepth--;
|
|
||||||
this.isComputing = false;
|
|
||||||
return isCaughtException(oldValue) || isCaughtException(this.value) || !this.is(oldValue, this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObxProperty(descriptor?: PropertyDescriptor) {
|
|
||||||
if (!descriptor || !descriptor.get) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (descriptor.get as any)[SYMBOL_OBX] ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureObxProperty(obj: any, prop: PropertyKey, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
|
||||||
if (!descriptor || isObxProperty(descriptor)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
defineObxProperty(obj, prop, undefined, descriptor, obxFlag);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function defineObxProperty(
|
|
||||||
obj: object,
|
|
||||||
key: PropertyKey,
|
|
||||||
val: any,
|
|
||||||
descriptor?: PropertyDescriptor,
|
|
||||||
obxFlag: ObxFlag = ObxFlag.DEEP,
|
|
||||||
): void {
|
|
||||||
if (!descriptor) {
|
|
||||||
descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
||||||
}
|
|
||||||
if (descriptor && descriptor.configurable === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val == null && descriptor && hasOwnProperty(descriptor, 'value')) {
|
|
||||||
val = descriptor.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getter = descriptor && descriptor.get;
|
|
||||||
const setter = descriptor && descriptor.set;
|
|
||||||
const property = new ObxProperty(String(key), obj, getter, setter, val, obxFlag);
|
|
||||||
const get = () => property.get();
|
|
||||||
(get as any)[SYMBOL_OBX] = property;
|
|
||||||
|
|
||||||
Object.defineProperty(obj, key, {
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
get,
|
|
||||||
set: (newVal) => property.set(newVal),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getObxProperty(obj: object, key: PropertyKey) {
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
||||||
|
|
||||||
if (!descriptor || !descriptor.get) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (descriptor.get as any)[SYMBOL_OBX] as ObxProperty;
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
import { addHiddenProp } from '../utils';
|
|
||||||
import Obx, { getObx, ObxFlag } from './obx';
|
|
||||||
import { reportChildValue } from './observable';
|
|
||||||
import { getProxiedValue } from './proxy';
|
|
||||||
import { setPrototypeOf } from '../../utils/set-prototype-of';
|
|
||||||
|
|
||||||
type SetType = Set<any> | WeakSet<any>;
|
|
||||||
|
|
||||||
export default class ObxSet extends Obx<SetType> {
|
|
||||||
constructor(name: string, target: SetType, obxFlag: ObxFlag = ObxFlag.DEEP) {
|
|
||||||
super(name, target, obxFlag);
|
|
||||||
|
|
||||||
setPrototypeOf(target, target instanceof Set ? setMethods : weaksetMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: PropertyKey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: PropertyKey, val: any) {}
|
|
||||||
|
|
||||||
get(key: PropertyKey) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
del(key: PropertyKey) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIterator(v: any): v is Iterator<any> {
|
|
||||||
return v.next ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchAccessor(keys: Array<string | symbol>, proto: any, methods: object): void {
|
|
||||||
keys.forEach(method => {
|
|
||||||
const original = proto[method];
|
|
||||||
addHiddenProp(methods, method, function accessor(this: any, ...args: any[]) {
|
|
||||||
const obx = getObx(this);
|
|
||||||
const flag = obx ? obx.obxFlag : ObxFlag.REF;
|
|
||||||
if (method === 'forEach') {
|
|
||||||
const fn = args[0];
|
|
||||||
const thisArg = args[0] || this;
|
|
||||||
args[0] = (v: any, a: any, c: any) => {
|
|
||||||
reportChildValue(v, flag);
|
|
||||||
return fn.call(thisArg, getProxiedValue(v), a, c);
|
|
||||||
};
|
|
||||||
return original.apply(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = original.apply(this, args);
|
|
||||||
|
|
||||||
if (method === 'get') {
|
|
||||||
reportChildValue(result, flag);
|
|
||||||
return getProxiedValue(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIterator(result)) {
|
|
||||||
const originNext = result.next;
|
|
||||||
const isMapIter = String(result) === '[object Map Iterator]';
|
|
||||||
const isEntries = method === 'entries';
|
|
||||||
let keys: string[] | null = null;
|
|
||||||
if (isEntries && !isMapIter) {
|
|
||||||
keys = ['0', '1'];
|
|
||||||
} else if (isMapIter && (isEntries || method === Symbol.iterator)) {
|
|
||||||
keys = ['1'];
|
|
||||||
}
|
|
||||||
|
|
||||||
result.next = function next() {
|
|
||||||
let n = originNext.call(this);
|
|
||||||
if (!n.done) {
|
|
||||||
if (keys) {
|
|
||||||
n.value = createResultProxy(n.value, flag, keys);
|
|
||||||
} else {
|
|
||||||
n = createResultProxy(n, flag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createResultProxy(entries: any, flag: ObxFlag, keys: any[] = ['value']) {
|
|
||||||
return new Proxy(entries, {
|
|
||||||
get(target, key) {
|
|
||||||
const v = target[key];
|
|
||||||
if (v && keys.includes(key)) {
|
|
||||||
reportChildValue(v, flag);
|
|
||||||
}
|
|
||||||
return getProxiedValue(v);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchMutator(keys: Array<string | symbol>, proto: any, methods: object): void {
|
|
||||||
keys.forEach(method => {
|
|
||||||
const original = proto[method];
|
|
||||||
addHiddenProp(methods, method, function mutator(this: any, ...args: any[]) {
|
|
||||||
const size = this.size;
|
|
||||||
const result = original.apply(this, args);
|
|
||||||
const obx = getObx(this);
|
|
||||||
let changed = true;
|
|
||||||
switch (method) {
|
|
||||||
case 'add':
|
|
||||||
case 'clear':
|
|
||||||
case 'delete':
|
|
||||||
changed = this.size !== size;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// now: "set" not compare values, default changed
|
|
||||||
if (obx && changed) {
|
|
||||||
obx.reportChange();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======= Set ========
|
|
||||||
const setProto = Set.prototype;
|
|
||||||
const setMethods = Object.create(setProto);
|
|
||||||
|
|
||||||
patchMutator(['add', 'clear', 'delete'], setProto, setMethods);
|
|
||||||
|
|
||||||
patchAccessor(['values', 'keys', 'entries', 'forEach', Symbol.iterator], setProto, setMethods);
|
|
||||||
|
|
||||||
// ======= WeakSet ========
|
|
||||||
const weaksetProto = WeakSet.prototype;
|
|
||||||
const weaksetMethods = Object.create(weaksetProto);
|
|
||||||
|
|
||||||
patchMutator(['add', 'delete', 'clear'], weaksetProto, weaksetMethods);
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
import { walk, addHiddenFinalProp, nextId } from '../utils';
|
|
||||||
import { defineObxProperty } from './obx-property';
|
|
||||||
import { IObservable, propagateChanged, startBatch, endBatch } from './observable';
|
|
||||||
import { IDerivation, DerivationState, clearObserving } from '../derivation';
|
|
||||||
import { hasOwnProperty } from '../utils/has-own-property';
|
|
||||||
import { splitPath } from '../utils/split-path';
|
|
||||||
|
|
||||||
export enum ObxFlag {
|
|
||||||
REF = 0,
|
|
||||||
VAL = 1,
|
|
||||||
SHALLOW = 2,
|
|
||||||
DEEP = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Obx<T = any[] | object> implements IObservable, IDerivation {
|
|
||||||
id = nextId();
|
|
||||||
localVer = 0;
|
|
||||||
observing: IObservable[] = [];
|
|
||||||
observers = new Set<IDerivation>();
|
|
||||||
dependenciesState = DerivationState.NOT_TRACKING;
|
|
||||||
lowestObserverState = DerivationState.UP_TO_DATE;
|
|
||||||
|
|
||||||
constructor(public name: string, public target: T, public obxFlag: ObxFlag = ObxFlag.DEEP) {}
|
|
||||||
|
|
||||||
onBecomeDirty() {
|
|
||||||
propagateChanged(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onBecomeUnobserved() {
|
|
||||||
clearObserving(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
reportChange(force = false) {
|
|
||||||
startBatch();
|
|
||||||
this.localVer++;
|
|
||||||
propagateChanged(this, force);
|
|
||||||
endBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this unused function, move to utils $getAsObx
|
|
||||||
getAsObx(path: PropertyKey): Obx<T | any[] | object> | void {
|
|
||||||
if (path === '') {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry = path;
|
|
||||||
let nestPath = '';
|
|
||||||
|
|
||||||
if (typeof path === 'string') {
|
|
||||||
const pathArray = splitPath(path);
|
|
||||||
|
|
||||||
if (!pathArray) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = pathArray[1];
|
|
||||||
nestPath = pathArray[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry) {
|
|
||||||
return this.get(nestPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = this.get(entry);
|
|
||||||
|
|
||||||
if (!hasObx(ret) && nestPath) {
|
|
||||||
ret = this.get(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
const obx = getObx(ret);
|
|
||||||
|
|
||||||
if (obx && nestPath) {
|
|
||||||
return obx.getAsObx(nestPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return obx;
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: PropertyKey): boolean {
|
|
||||||
if (key == null || key === '') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return key in this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key?: PropertyKey): any {
|
|
||||||
if (key == null || key === '') {
|
|
||||||
return this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this.target as any)[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: PropertyKey, val: any): void {
|
|
||||||
if (this.obxFlag > ObxFlag.REF) {
|
|
||||||
defineObxProperty(this.target as any, key, val, undefined, this.obxFlag);
|
|
||||||
} else {
|
|
||||||
(this.target as any)[key] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reportChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
del(key: PropertyKey) {
|
|
||||||
if (!hasOwnProperty(this.target, key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete (this.target as any)[key];
|
|
||||||
this.reportChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
extend(properties: object) {
|
|
||||||
startBatch();
|
|
||||||
walk(properties, (_, key, val) => this.set(key, val));
|
|
||||||
endBatch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Obx;
|
|
||||||
|
|
||||||
export const SYMBOL_OBX = Symbol.for('obx');
|
|
||||||
export function injectObx(obj: any[] | object, obx: Obx): void {
|
|
||||||
addHiddenFinalProp(obj, SYMBOL_OBX, obx);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getObx(obj: any): Obx | undefined {
|
|
||||||
return obj ? (obj as any)[SYMBOL_OBX] : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasObx(obj: any[] | object): boolean {
|
|
||||||
return hasOwnProperty(obj, SYMBOL_OBX) && (obj as any)[SYMBOL_OBX] instanceof Obx;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reportChange(obj: any, force = false) {
|
|
||||||
const obx = getObx(obj);
|
|
||||||
if (obx) {
|
|
||||||
obx.reportChange(force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { addHiddenFinalProp } from '../utils';
|
|
||||||
|
|
||||||
export const SYMBOL_PROXY = Symbol.for('proxy');
|
|
||||||
export const SYMBOL_RAW = Symbol.for('raw');
|
|
||||||
export interface Proxied<T> {
|
|
||||||
[SYMBOL_PROXY]: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isProxied<T>(target: T): target is T & Proxied<T> {
|
|
||||||
return SYMBOL_PROXY in target;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProxy<T>(target: T & Proxied<T>) {
|
|
||||||
return target[SYMBOL_PROXY];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setProxy(target: object, proxy: object) {
|
|
||||||
addHiddenFinalProp(target, SYMBOL_PROXY, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProxiedValue(target: any) {
|
|
||||||
return (target && getProxy(target)) || target;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRawValue(target: any) {
|
|
||||||
return (target && target[SYMBOL_RAW]) || target;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const supportProxy = 'Proxy' in global;
|
|
||||||
|
|
||||||
export function createProxy<T extends object>(target: T, taps: ProxyHandler<T>) {
|
|
||||||
if (isProxied(target)) {
|
|
||||||
return getProxy(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy = new Proxy(target, taps);
|
|
||||||
setProxy(target, proxy);
|
|
||||||
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
import { FunctionComponent, ComponentType } from 'react';
|
|
||||||
import { Component } from 'rax';
|
|
||||||
import { Reaction } from './reaction';
|
|
||||||
import { shallowEqual } from './utils';
|
|
||||||
|
|
||||||
const SYMBOL_REACTION = Symbol('__obxReaction');
|
|
||||||
const SYMBOL_ISUNMOUNTED = Symbol('__obxIsUnmounted');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ReactiveMixin
|
|
||||||
*/
|
|
||||||
function defaultComponentWillUnmount(this: any) {
|
|
||||||
this.render[SYMBOL_REACTION] && this.render[SYMBOL_REACTION].sleep();
|
|
||||||
this[SYMBOL_ISUNMOUNTED] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultShouldComponentUpdate(this: any, nextProps: any, nextState: any) {
|
|
||||||
if (this.state !== nextState) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !shallowEqual(this.props, nextProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// function shouldConstruct(C: any) {
|
|
||||||
// const prototype = C.prototype;
|
|
||||||
// return !!(prototype && prototype.isReactComponent);
|
|
||||||
// }
|
|
||||||
|
|
||||||
function shouldConstruct(C: any) {
|
|
||||||
const prototype = C.prototype;
|
|
||||||
return !!(prototype && prototype.constructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFunctionComponent<T = any>(type: Function): type is FunctionComponent<T> {
|
|
||||||
return !shouldConstruct(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getReaction(target: Component): Reaction | undefined {
|
|
||||||
return (target.render as any)[SYMBOL_REACTION];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observer function / decorator
|
|
||||||
*/
|
|
||||||
export function observer<T extends ComponentType<any>>(target: T): T {
|
|
||||||
if (!target) {
|
|
||||||
throw new Error('Please pass a valid component to "observer"');
|
|
||||||
}
|
|
||||||
if (typeof target !== 'function') {
|
|
||||||
throw new Error('obx observer: needs to be a react class constructor or stateless function components');
|
|
||||||
}
|
|
||||||
|
|
||||||
let componentClass: any = target;
|
|
||||||
|
|
||||||
if (isFunctionComponent(target)) {
|
|
||||||
componentClass = class extends Component {
|
|
||||||
static displayName = componentClass.displayName || componentClass.name;
|
|
||||||
static contextTypes = componentClass.contextTypes;
|
|
||||||
static propTypes = componentClass.propTypes;
|
|
||||||
static defaultProps = componentClass.defaultProps;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return target.call(this, this.props, this.context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const proto = componentClass.prototype || componentClass;
|
|
||||||
mixinLifecycleEvents(proto);
|
|
||||||
componentClass.isObxReactObserver = true;
|
|
||||||
const baseRender = proto.render;
|
|
||||||
proto.render = function() {
|
|
||||||
return makeComponentReactive.call(this, baseRender);
|
|
||||||
};
|
|
||||||
return componentClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeComponentReactive(this: any, render: any) {
|
|
||||||
function reactiveRender() {
|
|
||||||
isRenderingPending = false;
|
|
||||||
let exception = undefined;
|
|
||||||
let rendering = undefined;
|
|
||||||
reaction.track(() => {
|
|
||||||
try {
|
|
||||||
rendering = baseRender();
|
|
||||||
} catch (e) {
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (exception) {
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
return rendering || baseRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate friendly name for debugging
|
|
||||||
const initialName =
|
|
||||||
this.displayName ||
|
|
||||||
this.name ||
|
|
||||||
(this.constructor && (this.constructor.displayName || this.constructor.name)) ||
|
|
||||||
'<component>';
|
|
||||||
|
|
||||||
const rootNodeID = (this._reactInternalFiber && this._reactInternalFiber._debugID) || '*';
|
|
||||||
|
|
||||||
// wire up reactive render
|
|
||||||
const baseRender = render.bind(this);
|
|
||||||
let isRenderingPending = false;
|
|
||||||
const reaction = new Reaction(
|
|
||||||
`${initialName}#${rootNodeID}.render()`,
|
|
||||||
() => {
|
|
||||||
if (!isRenderingPending) {
|
|
||||||
isRenderingPending = true;
|
|
||||||
if (typeof this.componentWillReact === 'function') {
|
|
||||||
this.componentWillReact();
|
|
||||||
}
|
|
||||||
if (this[SYMBOL_ISUNMOUNTED] !== true) {
|
|
||||||
let hasError = true;
|
|
||||||
try {
|
|
||||||
Component.prototype.forceUpdate.call(this);
|
|
||||||
hasError = false;
|
|
||||||
} finally {
|
|
||||||
if (hasError) reaction.sleep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
this.$level || 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
(reactiveRender as any)[SYMBOL_REACTION] = reaction;
|
|
||||||
this.render = reactiveRender;
|
|
||||||
return reactiveRender.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mixinLifecycleEvents(target: any) {
|
|
||||||
if (!target.componentWillUnmount) {
|
|
||||||
target.componentWillUnmount = defaultComponentWillUnmount;
|
|
||||||
} else {
|
|
||||||
const originFunc = target.componentWillUnmount;
|
|
||||||
target.componentWillUnmount = function(this: any) {
|
|
||||||
originFunc.call(this);
|
|
||||||
defaultComponentWillUnmount.call(this);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target.shouldComponentUpdate) {
|
|
||||||
target.shouldComponentUpdate = defaultShouldComponentUpdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,252 +0,0 @@
|
|||||||
import {
|
|
||||||
DerivationState,
|
|
||||||
IDerivation,
|
|
||||||
runDerivedFunction,
|
|
||||||
isCaughtException,
|
|
||||||
shouldCompute,
|
|
||||||
clearObserving,
|
|
||||||
CaughtException,
|
|
||||||
} from './derivation';
|
|
||||||
import { nextTick } from './next-tick';
|
|
||||||
import { IObservable, endBatch, startBatch } from './observable/observable';
|
|
||||||
import { globalState } from './ global-state';
|
|
||||||
import { throttle } from './utils/throttle';
|
|
||||||
|
|
||||||
export function nextId() {
|
|
||||||
return (++globalState.guid).toString(36).toLocaleLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Reaction implements IDerivation {
|
|
||||||
observing: IObservable[] = [];
|
|
||||||
dependenciesState = DerivationState.NOT_TRACKING;
|
|
||||||
id = nextId();
|
|
||||||
scheduled = false;
|
|
||||||
run: () => void;
|
|
||||||
caughtException: any = null;
|
|
||||||
|
|
||||||
private sleeping = false;
|
|
||||||
private running = false;
|
|
||||||
|
|
||||||
constructor(public name: string, private check: () => void, public level: number = 0, throttleWait = 10) {
|
|
||||||
if (throttleWait > 0) {
|
|
||||||
this.run = throttle(this.runReaction.bind(this), throttleWait);
|
|
||||||
} else {
|
|
||||||
this.run = this.runReaction.bind(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onBecomeDirty() {
|
|
||||||
this.schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule() {
|
|
||||||
if (this.scheduled || this.sleeping) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.scheduled = true;
|
|
||||||
scheduleReaction(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
isDirty(): boolean {
|
|
||||||
return shouldCompute(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
runReaction() {
|
|
||||||
if (this.sleeping || this.running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startBatch();
|
|
||||||
if (shouldCompute(this)) {
|
|
||||||
this.caughtException = null;
|
|
||||||
try {
|
|
||||||
this.check();
|
|
||||||
} catch (e) {
|
|
||||||
this.caughtException = new CaughtException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
track(fn: () => void) {
|
|
||||||
startBatch();
|
|
||||||
this.running = true;
|
|
||||||
const result = runDerivedFunction(this, fn);
|
|
||||||
if (isCaughtException(result)) {
|
|
||||||
this.caughtException = result;
|
|
||||||
}
|
|
||||||
this.running = false;
|
|
||||||
if (this.sleeping) {
|
|
||||||
clearObserving(this);
|
|
||||||
}
|
|
||||||
endBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep() {
|
|
||||||
if (this.sleeping) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sleeping = true;
|
|
||||||
if (!this.running) {
|
|
||||||
startBatch();
|
|
||||||
clearObserving(this);
|
|
||||||
endBatch();
|
|
||||||
deScheduleReaction(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wakeup(sync = false) {
|
|
||||||
if (this.sleeping) {
|
|
||||||
this.sleeping = false;
|
|
||||||
if (sync) {
|
|
||||||
this.runReaction();
|
|
||||||
} else {
|
|
||||||
this.schedule();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let flushIndex = 0;
|
|
||||||
let flushWaiting = false;
|
|
||||||
|
|
||||||
function scheduleReaction(reaction: Reaction) {
|
|
||||||
const { pendingReactions, isRunningReactions } = globalState;
|
|
||||||
if (!isRunningReactions) {
|
|
||||||
pendingReactions.push(reaction);
|
|
||||||
} else {
|
|
||||||
let i = pendingReactions.length - 1;
|
|
||||||
// 0 1 2 3 4 5 6 7 8 9
|
|
||||||
// 0, 0, 1, 1, 1, 2, 2, 2, 3, 3
|
|
||||||
// ^ ^
|
|
||||||
// flushIndex = 2 level = 2
|
|
||||||
// break at: i = 7 or i = 2
|
|
||||||
while (i > flushIndex && pendingReactions[i].level > reaction.level) {
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
pendingReactions.splice(i + 1, 0, reaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
runReactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deScheduleReaction(reaction: Reaction) {
|
|
||||||
const { pendingReactions, isRunningReactions } = globalState;
|
|
||||||
if (!isRunningReactions) {
|
|
||||||
const i = pendingReactions.indexOf(reaction);
|
|
||||||
if (i > -1) {
|
|
||||||
pendingReactions.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runReactions() {
|
|
||||||
// queue the flush
|
|
||||||
if (!flushWaiting) {
|
|
||||||
flushWaiting = true;
|
|
||||||
nextTick(flushReactions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_REACTION_ITERATIONS = 100;
|
|
||||||
|
|
||||||
function flushReactions() {
|
|
||||||
globalState.isRunningReactions = true;
|
|
||||||
const allReactions = globalState.pendingReactions;
|
|
||||||
let pendingLength = 0;
|
|
||||||
let iterations = 0;
|
|
||||||
|
|
||||||
// low level run first
|
|
||||||
// sort as:
|
|
||||||
// 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 5
|
|
||||||
allReactions.sort((a, b) => a.level - b.level);
|
|
||||||
|
|
||||||
while (allReactions.length > pendingLength) {
|
|
||||||
pendingLength = allReactions.length;
|
|
||||||
if (++iterations === MAX_REACTION_ITERATIONS) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
console.error(
|
|
||||||
`Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
|
|
||||||
` Probably there is a cycle in the reactive function: ${allReactions[0]}`,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (; flushIndex < pendingLength; flushIndex++) {
|
|
||||||
allReactions[flushIndex].scheduled = false;
|
|
||||||
allReactions[flushIndex].run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flushIndex = 0;
|
|
||||||
flushWaiting = false;
|
|
||||||
allReactions.length = 0;
|
|
||||||
globalState.isRunningReactions = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearReactions() {
|
|
||||||
const { pendingReactions } = globalState;
|
|
||||||
let i = pendingReactions.length;
|
|
||||||
while (i--) {
|
|
||||||
pendingReactions[i].sleep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Disposer {
|
|
||||||
(): void;
|
|
||||||
name?: string;
|
|
||||||
$obx: Reaction;
|
|
||||||
}
|
|
||||||
export interface AutorunOptions {
|
|
||||||
throttle?: number;
|
|
||||||
sync?: boolean;
|
|
||||||
level?: number;
|
|
||||||
name?: string;
|
|
||||||
runFirstNow?: boolean;
|
|
||||||
}
|
|
||||||
export interface RunContext {
|
|
||||||
dispose: Disposer;
|
|
||||||
firstRun: boolean;
|
|
||||||
}
|
|
||||||
export type Action = (this: RunContext, context: RunContext) => any;
|
|
||||||
|
|
||||||
export function autorun(action: Action, options: number | true | AutorunOptions = {}): Disposer {
|
|
||||||
if (typeof options === 'number') {
|
|
||||||
options = { throttle: options };
|
|
||||||
} else if (options === true) {
|
|
||||||
options = { sync: true };
|
|
||||||
}
|
|
||||||
const name: string = options.name || (action as any).name || 'Autorun@' + nextId();
|
|
||||||
|
|
||||||
const reaction = new Reaction(
|
|
||||||
name,
|
|
||||||
function(this: Reaction) {
|
|
||||||
this.track(reactionRunner);
|
|
||||||
},
|
|
||||||
options.level || 0,
|
|
||||||
options.throttle || 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispose = () => {
|
|
||||||
reaction.sleep();
|
|
||||||
};
|
|
||||||
|
|
||||||
dispose.$obx = reaction;
|
|
||||||
|
|
||||||
let firstRun = true;
|
|
||||||
function reactionRunner() {
|
|
||||||
const ctx: RunContext = {
|
|
||||||
firstRun,
|
|
||||||
dispose,
|
|
||||||
};
|
|
||||||
action.call(ctx, ctx);
|
|
||||||
firstRun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.sync || options.runFirstNow) {
|
|
||||||
reaction.runReaction();
|
|
||||||
} else {
|
|
||||||
reaction.schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispose;
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export const prototypeHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
||||||
|
|
||||||
export function hasOwnProperty(obj: any, key: string | number | symbol): boolean {
|
|
||||||
return obj && prototypeHasOwnProperty.call(obj, key);
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
export * from './shallow-equal';
|
|
||||||
export * from './has-own-property';
|
|
||||||
export * from './throttle';
|
|
||||||
export * from './next-id';
|
|
||||||
export * from './is-primitive';
|
|
||||||
export * from './invariant';
|
|
||||||
export * from './splitPath';
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export function invariant(check: any, message: string, thing?: any) {
|
|
||||||
if (!check) {
|
|
||||||
throw new Error('[recore] Invariant failed: ' + message + (thing ? ` in '${thing}'` : ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
export function isPrimitive(obj: any): boolean {
|
|
||||||
// null | undefined
|
|
||||||
if (obj == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const t = typeof obj;
|
|
||||||
return t === 'boolean' || t === 'number' || t === 'string' || t === 'symbol';
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { globalState } from '../ global-state';
|
|
||||||
export function nextId() {
|
|
||||||
return (++globalState.guid).toString(36).toLocaleLowerCase();
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import { prototypeHasOwnProperty } from './has-own-property';
|
|
||||||
|
|
||||||
export function shallowEqual(objA: any, objB: any): boolean {
|
|
||||||
if (objA === objB) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keysA = Object.keys(objA);
|
|
||||||
const keysB = Object.keys(objB);
|
|
||||||
|
|
||||||
if (keysA.length !== keysB.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for A's keys different from B.
|
|
||||||
const bHasOwnProperty = prototypeHasOwnProperty.bind(objB);
|
|
||||||
for (let i = 0; i < keysA.length; i++) {
|
|
||||||
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
const RE_PATH = /^([^/]*)(?:\/(.*))?$/;
|
|
||||||
const RE_PATH_REVERSE = /^(?:(.*)\/)?([^/]+)$/;
|
|
||||||
export function splitPath(path: string, reverse = false) {
|
|
||||||
return reverse ? RE_PATH_REVERSE.exec(path) : RE_PATH.exec(path);
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
const useRAF = typeof requestAnimationFrame === 'function';
|
|
||||||
|
|
||||||
export function throttle(func: Function, wait: number) {
|
|
||||||
let lastArgs: any;
|
|
||||||
let lastThis: any;
|
|
||||||
let result: any;
|
|
||||||
let timerId: number | undefined;
|
|
||||||
let lastCalled: number | undefined;
|
|
||||||
let lastInvoked = 0;
|
|
||||||
|
|
||||||
function invoke(time: number) {
|
|
||||||
const args = lastArgs;
|
|
||||||
const thisArg = lastThis;
|
|
||||||
|
|
||||||
lastArgs = undefined;
|
|
||||||
lastThis = undefined;
|
|
||||||
lastInvoked = time;
|
|
||||||
result = func.apply(thisArg, args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTimer(pendingFunc: any, wait: number): number {
|
|
||||||
if (useRAF) {
|
|
||||||
return requestAnimationFrame(pendingFunc);
|
|
||||||
}
|
|
||||||
return setTimeout(pendingFunc, wait) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function leadingEdge(time: number) {
|
|
||||||
lastInvoked = time;
|
|
||||||
timerId = startTimer(timerExpired, wait);
|
|
||||||
return invoke(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldInvoke(time: number) {
|
|
||||||
const timeSinceLastCalled = time - lastCalled!;
|
|
||||||
const timeSinceLastInvoked = time - lastInvoked;
|
|
||||||
|
|
||||||
return (
|
|
||||||
lastCalled === undefined || timeSinceLastCalled >= wait || timeSinceLastCalled < 0 || timeSinceLastInvoked >= wait
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function remainingWait(time: number) {
|
|
||||||
const timeSinceLastCalled = time - lastCalled!;
|
|
||||||
const timeSinceLastInvoked = time - lastInvoked;
|
|
||||||
|
|
||||||
return Math.min(wait - timeSinceLastCalled, wait - timeSinceLastInvoked);
|
|
||||||
}
|
|
||||||
|
|
||||||
function timerExpired() {
|
|
||||||
const time = Date.now();
|
|
||||||
if (shouldInvoke(time)) {
|
|
||||||
return trailingEdge(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
timerId = startTimer(timerExpired, remainingWait(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
function trailingEdge(time: number) {
|
|
||||||
timerId = undefined;
|
|
||||||
|
|
||||||
if (lastArgs) {
|
|
||||||
return invoke(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastArgs = undefined;
|
|
||||||
lastThis = undefined;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function debounced(this: any, ...args: any[]) {
|
|
||||||
const time = Date.now();
|
|
||||||
const isInvoking = shouldInvoke(time);
|
|
||||||
|
|
||||||
lastArgs = args;
|
|
||||||
lastThis = this;
|
|
||||||
lastCalled = time;
|
|
||||||
|
|
||||||
if (isInvoking) {
|
|
||||||
if (timerId === undefined) {
|
|
||||||
return leadingEdge(lastCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
timerId = startTimer(timerExpired, wait);
|
|
||||||
return invoke(lastCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timerId === undefined) {
|
|
||||||
timerId = startTimer(timerExpired, wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return debounced;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user