From 516dbed5174801694aec7d2109ae8fec3cd63b91 Mon Sep 17 00:00:00 2001 From: Josep M Sobrepere Date: Mon, 10 Oct 2022 11:25:50 +0200 Subject: [PATCH] chore: avoid passing parentState to internal `run` --- packages/context-state/src/combineStates.ts | 46 +++++++++---------- packages/context-state/src/create-root.ts | 4 +- .../context-state/src/internal/children.ts | 7 ++- .../src/internal/detached-node.ts | 33 +++++++------ packages/context-state/src/route-state.ts | 25 +++------- packages/context-state/src/substate.ts | 2 +- 6 files changed, 50 insertions(+), 67 deletions(-) diff --git a/packages/context-state/src/combineStates.ts b/packages/context-state/src/combineStates.ts index b6f638a..46cb03b 100644 --- a/packages/context-state/src/combineStates.ts +++ b/packages/context-state/src/combineStates.ts @@ -1,12 +1,6 @@ import { NestedMap } from "./internal/nested-map" import { of } from "rxjs" -import { - mapRecord, - detachedNode, - EMPTY_VALUE, - recordEntries, - children, -} from "./internal" +import { mapRecord, detachedNode, recordEntries, children } from "./internal" import { StateNode, StringRecord } from "./types" type StringRecordNodeToNodeStringRecord< @@ -20,43 +14,47 @@ export const combineStates = >>( ): StringRecordNodeToNodeStringRecord => { const instances = new NestedMap() const nKeys = Object.keys(states).length - const _activeStates = mapRecord(states, () => false) - const _latestStates = mapRecord(states, () => null) + const _allFalse = mapRecord(states, () => false) const [result, run] = detachedNode((ctx) => of(mapRecord(states, (node) => ctx(node))), ) - let latestValue: boolean | EMPTY_VALUE = false recordEntries(states).forEach(([key, node]) => { - children.get(node)!.add((ctxKey, isActive, value) => { + children.get(node)!.add((ctxKey, isActive, isParentLoaded) => { let instance: any = instances.get(ctxKey) if (!instance) { instance = { inactiveStates: nKeys, - activeStates: { ..._activeStates }, - latestStates: { ..._latestStates }, + emptyStates: nKeys, + activeStates: { ..._allFalse }, + loadedStates: { ..._allFalse }, + latestIsActive: null, + latestIsLoaded: null, } instances.set(ctxKey, instance) } if (isActive !== instance.activeStates[key]) { - instance.inactiveStates += isActive ? -1 : +1 + instance.inactiveStates += isActive ? -1 : 1 instance.activeStates[key] = isActive } - if (value !== instance.latestStates[key]) { - instance.emptyStates += - instance.latestStates[key] === EMPTY_VALUE - ? -1 - : value === EMPTY_VALUE - ? 1 - : 0 - instance.latestStates[key] = value + if (isParentLoaded !== instance.loadedStates[key]) { + instance.emptyStates += isParentLoaded ? -1 : 1 + instance.loadedStates[key] = isParentLoaded } - latestValue = instance.emptyStates === 0 ? !latestValue : EMPTY_VALUE - run(ctxKey, instance.inactiveStates === 0, instance.latestValue) + const isCurrentlyActive = instance.inactiveStates === 0 + const isLoaded = instance.activeStates === nKeys + if ( + isCurrentlyActive !== instance.latestIsActive || + isLoaded !== instance.latestIsLoaded + ) { + instance.latestIsActive = isCurrentlyActive + instance.latestIsLoaded = isLoaded + run(ctxKey, isCurrentlyActive, isLoaded) + } }) }) diff --git a/packages/context-state/src/create-root.ts b/packages/context-state/src/create-root.ts index f9897c0..92b2848 100644 --- a/packages/context-state/src/create-root.ts +++ b/packages/context-state/src/create-root.ts @@ -7,11 +7,11 @@ export interface RootNode extends StateNode { export function createRoot(): RootNode { const childRunners = new Set< - (key: any, isActive: boolean, value: null) => void + (key: any, isActive: boolean, value?: boolean) => void >() const runChildren = (key: any, isActive: boolean) => { childRunners.forEach((cb) => { - cb(key, isActive, null) + cb(key, isActive, true) }) } diff --git a/packages/context-state/src/internal/children.ts b/packages/context-state/src/internal/children.ts index 3f942ea..1174093 100644 --- a/packages/context-state/src/internal/children.ts +++ b/packages/context-state/src/internal/children.ts @@ -1,8 +1,7 @@ import { StateNode } from "../types" -import { EMPTY_VALUE } from "./empty-value" -export interface RunFn

{ - (key: any, isActive: boolean, parentValue?: P | EMPTY_VALUE): void +export interface RunFn { + (key: any[], isActive: boolean, isParentLoaded?: boolean): void } -export const children = new WeakMap, Set>>() +export const children = new WeakMap, Set>() diff --git a/packages/context-state/src/internal/detached-node.ts b/packages/context-state/src/internal/detached-node.ts index 0e7d1c4..38ae6d1 100644 --- a/packages/context-state/src/internal/detached-node.ts +++ b/packages/context-state/src/internal/detached-node.ts @@ -12,17 +12,17 @@ import { } from "./" import { NestedMap } from "./nested-map" -export const detachedNode = ( +export const detachedNode = ( getState$: (ctx: Ctx) => Observable, equalityFn: (a: T, b: T) => boolean = Object.is, -): [StateNode, RunFn

] => { +): [StateNode, RunFn] => { const instances = new NestedMap< any, { subject: ReplaySubject subscription: Subscription | null currentValue: EMPTY_VALUE | T - currentParentValue: EMPTY_VALUE + isParentLoaded: boolean promise: DeferredPromise | null } >() @@ -45,16 +45,16 @@ export const detachedNode = ( }), } - const childRunners = new Set>() + const childRunners = new Set() children.set(result, childRunners) - const runChildren: RunFn = (...args) => { + const runChildren: RunFn = (...args) => { childRunners.forEach((cb) => { cb(...args) }) } - const run = (key: any[], isActive: boolean, parentValue: any) => { + const run = (key: any[], isActive: boolean, isParentLoaded?: boolean) => { let instance = instances.get(key) if (!isActive) { if (!instance) return @@ -68,7 +68,7 @@ export const detachedNode = ( return } - if (parentValue !== EMPTY_VALUE) { + if (isParentLoaded) { // an actual change of context const hasPreviousValue = instance && instance.currentValue !== EMPTY_VALUE if (!instance) { @@ -76,14 +76,14 @@ export const detachedNode = ( subject: new ReplaySubject(1), subscription: null, currentValue: EMPTY_VALUE, - currentParentValue: EMPTY_VALUE, + isParentLoaded, promise: null, } instances.set(key, instance) } else { instance.subscription?.unsubscribe() instance.currentValue = EMPTY_VALUE - instance.currentParentValue = parentValue + instance.isParentLoaded = true } const actualInstance = instance @@ -103,7 +103,6 @@ export const detachedNode = ( delete (actualInstance as any).subject actualInstance.currentValue = EMPTY_VALUE - actualInstance.currentParentValue = EMPTY_VALUE runChildren(key, false) prevPromise?.rej(err) @@ -126,7 +125,7 @@ export const detachedNode = ( actualInstance.promise = null if (prevValue === EMPTY_VALUE || !equalityFn(prevValue, value)) { prevPromise?.res(value) - runChildren(key, true, value) + runChildren(key, true, true) actualInstance.subject!.next(value) } }, @@ -148,32 +147,32 @@ export const detachedNode = ( prevSubect = actualInstance.subject actualInstance.subject = new ReplaySubject(1) } - runChildren(key, true, EMPTY_VALUE) + runChildren(key, true, false) prevSubect?.complete() } return } - // at this point parentValue is EMPTY_VALUE - if (instance?.currentParentValue === EMPTY_VALUE) return + if (instance?.isParentLoaded === false) return + const prevSubect = instance?.subject if (instance) { instance.subject = new ReplaySubject(1) instance.subscription?.unsubscribe() instance.subscription = null instance.currentValue = EMPTY_VALUE - instance.currentParentValue = EMPTY_VALUE + instance.isParentLoaded = false } else { instance = { subject: new ReplaySubject(1), subscription: null, currentValue: EMPTY_VALUE, - currentParentValue: EMPTY_VALUE, + isParentLoaded: false, promise: null, } instances.set(key, instance) } - runChildren(key, true, EMPTY_VALUE) + runChildren(key, true, false) prevSubect?.complete() } diff --git a/packages/context-state/src/route-state.ts b/packages/context-state/src/route-state.ts index 5bf8d04..794095b 100644 --- a/packages/context-state/src/route-state.ts +++ b/packages/context-state/src/route-state.ts @@ -1,10 +1,4 @@ -import { - EMPTY_VALUE, - children, - mapRecord, - detachedNode, - recordEntries, -} from "./internal" +import { children, mapRecord, detachedNode, recordEntries } from "./internal" import { of } from "rxjs" import { substate } from "./substate" import { StateNode, Ctx } from "./types" @@ -27,7 +21,7 @@ export const routeState = < const keyState = substate(parent, (ctx) => of(selector(ctx(parent), ctx))) const routedState = mapRecord(routes, (mapper) => - detachedNode((ctx) => { + detachedNode((ctx) => { const parentValue = ctx(parent) return of(mapper ? mapper(parentValue) : parentValue) }), @@ -37,18 +31,11 @@ export const routeState = < recordEntries(routedState).map(([key, value]) => [key, value[1]]), ) - const run = ( - ctxKey: any[], - isActive: boolean, - value: keyof O | EMPTY_VALUE, - ) => { - if (!isActive || value === EMPTY_VALUE) - runners.forEach((runner) => { - runner(ctxKey, false) - }) - + const run = (ctxKey: any[], isActive: boolean, isParentLoaded?: boolean) => { + const activeKey = + isActive && isParentLoaded ? keyState.getValue(ctxKey) : null runners.forEach((runner, key) => { - if (key === value) runner(ctxKey, true, value) + if (key === activeKey) runner(ctxKey, true, true) else runner(ctxKey, false) }) } diff --git a/packages/context-state/src/substate.ts b/packages/context-state/src/substate.ts index ee5dc2c..a1087f3 100644 --- a/packages/context-state/src/substate.ts +++ b/packages/context-state/src/substate.ts @@ -7,7 +7,7 @@ export const substate = ( getState$: (ctx: Ctx) => Observable, equalityFn: (a: T, b: T) => boolean = Object.is, ): StateNode => { - const [result, run] = detachedNode(getState$, equalityFn) + const [result, run] = detachedNode(getState$, equalityFn) children.get(parent)!.add(run) return result }