wana
Version:
Easy observable state for React
86 lines (75 loc) • 2.23 kB
text/typescript
import { is } from '@alloc/is'
import { Auto } from './auto'
import { batch } from './batch'
import { Disposable, setHidden } from './common'
import { emitReplace } from './emit'
import { observe } from './globals'
import { noto } from './noto'
import { Observable } from './observable'
import { $O } from './symbols'
/**
* An observable getter that memoizes its result.
*
* The memoization is observed, so when a dependency changes,
* the memoized value is released and observers are notified.
*/
export interface Derived<T = any> extends Disposable {
/** The underlying observable */
[$O]?: Observable
/** The underlying observer */
auto: Auto
/** Get the current value */
(): T
}
/** @internal */
export function derive<T>(
run: (auto: Auto) => T,
discard?: (memo: T, oldMemo: T | undefined) => boolean
): Derived<T> {
// The memoized result
let memo: T | undefined
// For being observed
const observable = new Observable()
// For observing others
const auto = new Auto({
onDirty() {
if (observable.has($O)) {
batch.run(derived)
// Tell our observers to join the next batch to see if our `memo`
// value has changed.
// Avoid mutating the `nonce` of our observers until our getter
// produces a new value (which may never happen).
observable.get($O).onChange({
op: 'clear',
target: derived,
oldValue: memo,
})
}
},
})
// The observable getter
const derived = () => {
if (auto.dirty) {
noto(() => {
const oldMemo = memo
const newMemo = run(auto)
if (newMemo !== oldMemo && !(discard && discard(newMemo, oldMemo))) {
emitReplace(derived, null, (memo = newMemo), oldMemo)
}
})
}
observe(derived, $O)
return memo!
}
derived.auto = auto
derived.dispose = () => auto.dispose()
setHidden(derived, $O, observable)
return derived
}
export function isDerived(value: unknown): value is Derived {
return is.function(value) && !!value[$O]
}
/** Convert all `Derived<T>` property types into `T` */
export type WithDerived<T extends object> = {
[P in keyof T]: T[P] extends Derived<infer U> ? U : T[P]
}