Last active
August 14, 2024 13:06
-
-
Save aziis98/c8de74a29d5b1721983501fe28eb25e2 to your computer and use it in GitHub Desktop.
Lexical "hooks" in JS using TemplateStringsArray to distinguish callsites (in contrast to react hooks that use a form of stack/dynamic scoping)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Library | |
// | |
const newValueToUpdater = newValue => (typeof newValue === 'function' ? newValue : () => newValue) | |
const hooks = { | |
state: (hookContext, initialValue) => { | |
if (!hookContext.initialized) { | |
hookContext.initialized = true | |
hookContext.state = initialValue | |
} | |
return [ | |
hookContext.state, | |
newValue => { | |
hookContext.state = newValueToUpdater(newValue)(hookContext.state) | |
}, | |
] | |
}, | |
memo: (hookContext, fn, deps) => { | |
if (!hookContext.initialized) { | |
hookContext.initialized = true | |
hookContext.value = fn() | |
hookContext.deps = deps | |
} | |
if (deps.any(dep => hookContext.deps === undefined || hookContext.deps[dep] !== deps[dep])) { | |
hookContext.value = fn() | |
hookContext.deps = deps | |
} | |
return hookContext.value | |
}, | |
effect: (hookContext, fn, deps) => { | |
if (!hookContext.initialized) { | |
// console.log('effect init', deps) | |
hookContext.initialized = true | |
hookContext.cleanup = fn() | |
hookContext.deps = deps | |
} else { | |
// console.log('effect update', hookContext.deps, deps) | |
if (deps.some((dep, i) => dep !== hookContext.deps[i])) { | |
hookContext.cleanup() | |
hookContext.cleanup = fn() | |
hookContext.deps = deps | |
} | |
} | |
}, | |
} | |
const m = new WeakMap() | |
function use(strings) { | |
const key = strings[0] | |
const hook = hooks[key] | |
if (!hook) { | |
throw new Error(`Unknown hook: ${key}`) | |
} | |
return (...hookArgs) => { | |
let hookContext = m.get(strings) | |
if (hookContext === undefined) { | |
hookContext = {} | |
m.set(strings, hookContext) | |
} | |
return hook(hookContext, ...hookArgs) | |
} | |
} | |
// | |
// Example | |
// | |
function foo() { | |
const [value, setValue] = use`state`(-1) | |
console.log('foo state', value) | |
bar(setValue) | |
bar(setValue) | |
} | |
function bar(setValue) { | |
const [barValue, setBarValue] = use`state`(0) | |
console.log('bar state', barValue) | |
setValue(v => v * 2) | |
setBarValue(v => v + 1) | |
use`effect`(() => { | |
console.log('bar divisibility by 3 changed!', barValue) | |
return () => console.log('cleanup after', barValue) | |
}, [barValue % 3 === 0]) | |
} | |
foo() | |
foo() | |
foo() | |
foo() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
foo state -1 | |
bar state 0 | |
bar divisibility by 3 changed! 0 | |
bar state 1 | |
cleanup after 0 | |
bar divisibility by 3 changed! 1 | |
foo state -4 | |
bar state 2 | |
bar state 3 | |
cleanup after 1 | |
bar divisibility by 3 changed! 3 | |
foo state -16 | |
bar state 4 | |
cleanup after 3 | |
bar divisibility by 3 changed! 4 | |
bar state 5 | |
foo state -64 | |
bar state 6 | |
cleanup after 4 | |
bar divisibility by 3 changed! 6 | |
bar state 7 | |
cleanup after 6 | |
bar divisibility by 3 changed! 7 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment