wana
Version:
Easy observable state for React
108 lines (96 loc) • 2.3 kB
text/typescript
import { is } from '@alloc/is'
import { globals } from './globals'
import { noto } from './noto'
import { Change, Observable, ObservedSlot, ObserverTarget } from './observable'
import { $O, SIZE } from './symbols'
const { get } = Map.prototype
function onChange(observable: Observable, key: any, change: Change) {
const observers: ObservedSlot = get.call(observable, key)
if (observers) {
// Increase the nonce even if no observers exist, because there
// might be a pending observer (like a "withAuto" component).
observers.nonce++
observers.onChange(change)
}
}
function emit(target: ObserverTarget, change: Change) {
noto(() => {
const observable = target[$O]!
if (globals.onChange) {
globals.onChange(change)
}
// The "clear" op never has a key.
if (change.op !== 'clear') {
onChange(observable, change.key, change)
}
// When a `Map` object is cleared, notify every key observer.
else if (is.map(change.oldValue)) {
change.oldValue.forEach((_, key) => onChange(observable, key, change))
}
// Size changes always come after a related change,
// so avoid notifying `$O` observers more than once.
if (change.key !== SIZE) {
onChange(observable, $O, change)
}
})
return true
}
export const emitAdd = (target: object, key: any, value: any) =>
emit(target, {
op: 'add',
target,
key,
value,
})
export const emitRemove = (target: object, key: any, oldValue: any) =>
emit(target, {
op: 'remove',
target,
key,
oldValue,
})
export const emitReplace = (
target: object,
key: any,
value: any,
oldValue: any
) =>
emit(target, {
op: 'replace',
target,
key,
value,
oldValue,
})
export const emitSplice = (
target: any[],
key: number,
value: readonly any[],
oldValue: readonly any[]
) =>
emit(target, {
op: 'splice',
target,
key,
value,
oldValue,
})
export const emitClear = (target: object, oldValue: any) =>
emit(target, {
op: 'clear',
target,
oldValue,
})
export const emitDefine = (
target: object,
key: keyof any,
value: PropertyDescriptor,
oldValue?: PropertyDescriptor
) =>
emit(target, {
op: 'define',
target,
key,
value,
oldValue,
})