Skip to content

Instantly share code, notes, and snippets.

@trongthanh
Forked from getify/1.js
Last active November 1, 2018 04:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trongthanh/d02f58441848d29082dde7ec31a119d1 to your computer and use it in GitHub Desktop.
Save trongthanh/d02f58441848d29082dde7ec31a119d1 to your computer and use it in GitHub Desktop.
experiment: mimicking React's new "useState()" hook for stand-alone functions.
/**
* 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
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