chore: avoid passing parentState to internal run

This commit is contained in:
Josep M Sobrepere 2022-10-10 11:25:50 +02:00
parent 96b1997c95
commit 516dbed517
No known key found for this signature in database
GPG Key ID: 9A207FDA2481C91A
6 changed files with 50 additions and 67 deletions

View File

@ -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 = <States extends StringRecord<StateNode<any>>>(
): StringRecordNodeToNodeStringRecord<States> => {
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)
}
})
})

View File

@ -7,11 +7,11 @@ export interface RootNode extends StateNode<never> {
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)
})
}

View File

@ -1,8 +1,7 @@
import { StateNode } from "../types"
import { EMPTY_VALUE } from "./empty-value"
export interface RunFn<P> {
(key: any, isActive: boolean, parentValue?: P | EMPTY_VALUE): void
export interface RunFn {
(key: any[], isActive: boolean, isParentLoaded?: boolean): void
}
export const children = new WeakMap<StateNode<any>, Set<RunFn<any>>>()
export const children = new WeakMap<StateNode<any>, Set<RunFn>>()

View File

@ -12,17 +12,17 @@ import {
} from "./"
import { NestedMap } from "./nested-map"
export const detachedNode = <T, P>(
export const detachedNode = <T>(
getState$: (ctx: Ctx) => Observable<T>,
equalityFn: (a: T, b: T) => boolean = Object.is,
): [StateNode<T>, RunFn<P>] => {
): [StateNode<T>, RunFn] => {
const instances = new NestedMap<
any,
{
subject: ReplaySubject<T>
subscription: Subscription | null
currentValue: EMPTY_VALUE | T
currentParentValue: EMPTY_VALUE
isParentLoaded: boolean
promise: DeferredPromise<T> | null
}
>()
@ -45,16 +45,16 @@ export const detachedNode = <T, P>(
}),
}
const childRunners = new Set<RunFn<T>>()
const childRunners = new Set<RunFn>()
children.set(result, childRunners)
const runChildren: RunFn<T> = (...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 = <T, P>(
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 = <T, P>(
subject: new ReplaySubject<T>(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 = <T, P>(
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 = <T, P>(
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 = <T, P>(
prevSubect = actualInstance.subject
actualInstance.subject = new ReplaySubject<T>(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<T>(1)
instance.subscription?.unsubscribe()
instance.subscription = null
instance.currentValue = EMPTY_VALUE
instance.currentParentValue = EMPTY_VALUE
instance.isParentLoaded = false
} else {
instance = {
subject: new ReplaySubject<T>(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()
}

View File

@ -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<any, any>((ctx) => {
detachedNode<any>((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)
})
}

View File

@ -7,7 +7,7 @@ export const substate = <T, P>(
getState$: (ctx: Ctx) => Observable<T>,
equalityFn: (a: T, b: T) => boolean = Object.is,
): StateNode<T> => {
const [result, run] = detachedNode<T, P>(getState$, equalityFn)
const [result, run] = detachedNode<T>(getState$, equalityFn)
children.get(parent)!.add(run)
return result
}