doc + optimize: prism.ts (use Map
s)
This commit is contained in:
parent
1387ce62d2
commit
abd79b197f
1 changed files with 54 additions and 40 deletions
|
@ -121,7 +121,7 @@ class PrismScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupScopeStack(scope: PrismScope) {
|
function cleanupScopeStack(scope: PrismScope) {
|
||||||
for (const [_, sub] of Object.entries(scope.subs)) {
|
for (const sub of Object.values(scope.subs)) {
|
||||||
cleanupScopeStack(sub)
|
cleanupScopeStack(sub)
|
||||||
}
|
}
|
||||||
cleanupEffects(scope)
|
cleanupEffects(scope)
|
||||||
|
@ -130,8 +130,7 @@ function cleanupScopeStack(scope: PrismScope) {
|
||||||
function cleanupEffects(scope: PrismScope) {
|
function cleanupEffects(scope: PrismScope) {
|
||||||
const effects = effectsWeakMap.get(scope)
|
const effects = effectsWeakMap.get(scope)
|
||||||
if (effects) {
|
if (effects) {
|
||||||
for (const k of Object.keys(effects)) {
|
for (const effect of effects.values()) {
|
||||||
const effect = effects[k]
|
|
||||||
safelyRun(effect.cleanup, undefined)
|
safelyRun(effect.cleanup, undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,35 +140,34 @@ function cleanupEffects(scope: PrismScope) {
|
||||||
function safelyRun<T, U>(
|
function safelyRun<T, U>(
|
||||||
fn: () => T,
|
fn: () => T,
|
||||||
returnValueInCaseOfError: U,
|
returnValueInCaseOfError: U,
|
||||||
): {success: boolean; returnValue: T | U} {
|
): {ok: true; value: T} | {ok: false; value: U} {
|
||||||
let returnValue: T | U = returnValueInCaseOfError
|
|
||||||
let success = false
|
|
||||||
try {
|
try {
|
||||||
returnValue = fn()
|
return {value: fn(), ok: true}
|
||||||
success = true
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setTimeout(() => {
|
// Naming this function can allow the error reporter additional context to the user on where this error came from
|
||||||
|
setTimeout(function PrismReportThrow() {
|
||||||
|
// ensure that the error gets reported, but does not crash the current execution scope
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
return {value: returnValueInCaseOfError, ok: false}
|
||||||
}
|
}
|
||||||
return {success, returnValue}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hookScopeStack = new Stack<PrismScope>()
|
const hookScopeStack = new Stack<PrismScope>()
|
||||||
|
|
||||||
const refsWeakMap = new WeakMap<PrismScope, Record<string, IRef<unknown>>>()
|
const refsWeakMap = new WeakMap<PrismScope, Map<string, IRef<unknown>>>()
|
||||||
|
|
||||||
type IRef<T> = {
|
type IRef<T> = {
|
||||||
current: T
|
current: T
|
||||||
}
|
}
|
||||||
const effectsWeakMap = new WeakMap<PrismScope, Record<string, IEffect>>()
|
const effectsWeakMap = new WeakMap<PrismScope, Map<string, IEffect>>()
|
||||||
|
|
||||||
type IEffect = {
|
type IEffect = {
|
||||||
deps: undefined | unknown[]
|
deps: undefined | unknown[]
|
||||||
cleanup: VoidFn
|
cleanup: VoidFn
|
||||||
}
|
}
|
||||||
|
|
||||||
const memosWeakMap = new WeakMap<PrismScope, Record<string, IMemo>>()
|
const memosWeakMap = new WeakMap<PrismScope, Map<string, IMemo>>()
|
||||||
|
|
||||||
type IMemo = {
|
type IMemo = {
|
||||||
deps: undefined | unknown[] | ReadonlyArray<unknown>
|
deps: undefined | unknown[] | ReadonlyArray<unknown>
|
||||||
|
@ -182,29 +180,29 @@ function ref<T>(key: string, initialValue: T): IRef<T> {
|
||||||
throw new Error(`prism.ref() is called outside of a prism() call.`)
|
throw new Error(`prism.ref() is called outside of a prism() call.`)
|
||||||
}
|
}
|
||||||
let refs = refsWeakMap.get(scope)
|
let refs = refsWeakMap.get(scope)
|
||||||
if (!refs) {
|
if (refs === undefined) {
|
||||||
refs = {}
|
refs = new Map()
|
||||||
refsWeakMap.set(scope, refs)
|
refsWeakMap.set(scope, refs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refs[key]) {
|
let ref = refs.get(key)
|
||||||
return refs[key] as $IntentionalAny as IRef<T>
|
if (ref !== undefined) {
|
||||||
|
return ref as $IntentionalAny as IRef<T>
|
||||||
} else {
|
} else {
|
||||||
const ref: IRef<T> = {
|
const ref = {
|
||||||
current: initialValue,
|
current: initialValue,
|
||||||
}
|
}
|
||||||
refs[key] = ref
|
refs.set(key, ref)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An effect hook, similar to react's `useEffect()`.
|
* An effect hook, similar to React's `useEffect()`, but is not sensitive to call order by using `key`.
|
||||||
*
|
*
|
||||||
* @param key - the key for the effect. Should be uniqe inside of the prism.
|
* @param key - the key for the effect. Should be uniqe inside of the prism.
|
||||||
* @param cb - the callback function. Optionally returns a cleanup function.
|
* @param cb - the callback function. Requires returning a cleanup function.
|
||||||
* @param deps - the dependency array
|
* @param deps - the dependency array
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
function effect(key: string, cb: () => () => void, deps?: unknown[]): void {
|
function effect(key: string, cb: () => () => void, deps?: unknown[]): void {
|
||||||
const scope = hookScopeStack.peek()
|
const scope = hookScopeStack.peek()
|
||||||
|
@ -213,24 +211,25 @@ function effect(key: string, cb: () => () => void, deps?: unknown[]): void {
|
||||||
}
|
}
|
||||||
let effects = effectsWeakMap.get(scope)
|
let effects = effectsWeakMap.get(scope)
|
||||||
|
|
||||||
if (!effects) {
|
if (effects === undefined) {
|
||||||
effects = {}
|
effects = new Map()
|
||||||
effectsWeakMap.set(scope, effects)
|
effectsWeakMap.set(scope, effects)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!effects[key]) {
|
let effect = effects.get(key)
|
||||||
effects[key] = {
|
if (effect === undefined) {
|
||||||
|
effect = {
|
||||||
cleanup: voidFn,
|
cleanup: voidFn,
|
||||||
deps: [{}],
|
deps: undefined,
|
||||||
}
|
}
|
||||||
|
effects.set(key, effect)
|
||||||
}
|
}
|
||||||
|
|
||||||
const effect = effects[key]
|
|
||||||
if (depsHaveChanged(effect.deps, deps)) {
|
if (depsHaveChanged(effect.deps, deps)) {
|
||||||
effect.cleanup()
|
effect.cleanup()
|
||||||
|
|
||||||
startIgnoringDependencies()
|
startIgnoringDependencies()
|
||||||
effect.cleanup = safelyRun(cb, voidFn).returnValue
|
effect.cleanup = safelyRun(cb, voidFn).value
|
||||||
stopIgnoringDependencies()
|
stopIgnoringDependencies()
|
||||||
effect.deps = deps
|
effect.deps = deps
|
||||||
}
|
}
|
||||||
|
@ -242,13 +241,26 @@ function depsHaveChanged(
|
||||||
): boolean {
|
): boolean {
|
||||||
if (oldDeps === undefined || newDeps === undefined) {
|
if (oldDeps === undefined || newDeps === undefined) {
|
||||||
return true
|
return true
|
||||||
} else if (oldDeps.length !== newDeps.length) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return oldDeps.some((el, i) => el !== newDeps[i])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const len = oldDeps.length
|
||||||
|
if (len !== newDeps.length) return true
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
if (oldDeps[i] !== newDeps[i]) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a value to this {@link prism} stack.
|
||||||
|
*
|
||||||
|
* Unlike hooks seen in popular frameworks like React, you provide an exact `key` so
|
||||||
|
* we can call `prism.memo` in any order, and conditionally.
|
||||||
|
*
|
||||||
|
* @param deps - Passing in `undefined` will always cause a recompute
|
||||||
|
*/
|
||||||
function memo<T>(
|
function memo<T>(
|
||||||
key: string,
|
key: string,
|
||||||
fn: () => T,
|
fn: () => T,
|
||||||
|
@ -262,22 +274,24 @@ function memo<T>(
|
||||||
let memos = memosWeakMap.get(scope)
|
let memos = memosWeakMap.get(scope)
|
||||||
|
|
||||||
if (!memos) {
|
if (!memos) {
|
||||||
memos = {}
|
memos = new Map()
|
||||||
memosWeakMap.set(scope, memos)
|
memosWeakMap.set(scope, memos)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!memos[key]) {
|
let memo = memos.get(key)
|
||||||
memos[key] = {
|
if (memo === undefined) {
|
||||||
|
memo = {
|
||||||
cachedValue: null,
|
cachedValue: null,
|
||||||
deps: [{}],
|
// undefined will always indicate "deps have changed", so we set it's initial value as such
|
||||||
|
deps: undefined,
|
||||||
}
|
}
|
||||||
|
memos.set(key, memo)
|
||||||
}
|
}
|
||||||
|
|
||||||
const memo = memos[key]
|
|
||||||
if (depsHaveChanged(memo.deps, deps)) {
|
if (depsHaveChanged(memo.deps, deps)) {
|
||||||
startIgnoringDependencies()
|
startIgnoringDependencies()
|
||||||
|
|
||||||
memo.cachedValue = safelyRun(fn, undefined).returnValue
|
memo.cachedValue = safelyRun(fn, undefined).value
|
||||||
stopIgnoringDependencies()
|
stopIgnoringDependencies()
|
||||||
memo.deps = deps
|
memo.deps = deps
|
||||||
}
|
}
|
||||||
|
@ -368,7 +382,7 @@ function scope<T>(key: string, fn: () => T): T {
|
||||||
}
|
}
|
||||||
const subScope = parentScope.sub(key)
|
const subScope = parentScope.sub(key)
|
||||||
hookScopeStack.push(subScope)
|
hookScopeStack.push(subScope)
|
||||||
const ret = safelyRun(fn, undefined).returnValue
|
const ret = safelyRun(fn, undefined).value
|
||||||
hookScopeStack.pop()
|
hookScopeStack.pop()
|
||||||
return ret as $IntentionalAny as T
|
return ret as $IntentionalAny as T
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue