experiment: mimicking React's new "useState()" hook for stand-alone functions.
This file contains 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
/** | |
* Note: I have forked this gist from @getify to rewrite it with variable naming with closer association with hook and state | |
* and added some comments of my own so that it is easier to follow | |
* A demo is live at: https://codepen.io/trongthanh/pen/WaqqoJ?editors=0012 | |
*/ | |
'use strict'; | |
[foo, bar] = enableHooks(foo, bar); | |
function foo(origX, origY) { | |
var [x, setX] = useState(origX); | |
var [y, setY] = useState(origY); | |
console.log(`foo: ${x} ${y}`); | |
setX( x * 2 ); | |
setY( bar(y) ); | |
return origX; | |
} | |
function bar(curY) { | |
var [z, setZ] = useState(curY + 1); | |
console.log(`bar: ${z}`); | |
z = z * curY; | |
setZ( z ); | |
return z; | |
} | |
foo(3, 9); | |
// foo: 3 9 | |
// bar: 10 | |
foo(); | |
// foo: 6 90 | |
// bar: 90 | |
foo(); | |
// foo: 12 8100 | |
// bar: 8100 |
This file contains 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
var { enableHooks, useState } = (function def() { | |
'use strict'; | |
var hookFnMap = new WeakMap(); | |
var hookFnQueue = []; | |
return { enableHooks, useState }; | |
// ****************** | |
// (make use of functions hosting, so it's easier to read) | |
// Let's call the functions enabled for use with hooks are hookFns | |
// (Initially, I thought of naming them hookers =)) | |
// Please be aware that this gist only address the useState scenario of React Hooks | |
function enableHooks(...fns) { | |
const mappedFns = fns.map((fn) => { | |
return function hookFn(...args) { | |
if (hookFnMap.has(hookFn)) { | |
// reset the state slot index | |
let stateBucket = hookFnMap.get(hookFn); | |
stateBucket.nextIdx = 0; | |
// enableHooks may be called on a function without any useState() | |
// so we don't create a stateBucket until a useState is actually called | |
} | |
// insert the hookFn at the end of the executing queue to be checked by useState | |
hookFnQueue.push(hookFn); | |
try { | |
return fn.apply(this, args); // `this` is what current hookFn is bound to | |
} finally { | |
// remove current hookFn from queue once it's done | |
hookFnQueue.pop(); | |
} | |
}; | |
}); | |
if (mappedFns.length < 2) { | |
// don't return array, if 0 or 1 | |
return mappedFns[0]; | |
} | |
return mappedFns; | |
} | |
function useState(defaultValue) { | |
if (hookFnQueue.length > 0) { | |
// currently executing hook function | |
let hookFn = hookFnQueue[hookFnQueue.length - 1]; | |
let stateBucket; | |
if (!hookFnMap.has(hookFn)) { | |
// this is the first time useState() is called inside a hookFn | |
stateBucket = { nextIdx: 0, slots: [] }; | |
hookFnMap.set(hookFn, stateBucket); | |
} | |
stateBucket = hookFnMap.get(hookFn); | |
if (!(stateBucket.nextIdx in stateBucket.slots)) { | |
// this is the very first time the hookFn is called, let's use the defaultValue | |
// each slot is an array of [ stateValue, setStateFn ] | |
let slot = [ defaultValue, function updateSlot(v) { | |
slot[0] = v; | |
}]; | |
stateBucket.slots[stateBucket.nextIdx] = slot; | |
} | |
// return the [ stateValue, setStateFn ] and increase the slot nextIdx | |
return [...stateBucket.slots[stateBucket.nextIdx++]]; | |
} else { | |
throw new Error('Only use useState() inside hooked-enabled functions.'); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment