Created
November 16, 2021 20:56
-
-
Save DV8FromTheWorld/d422272be3681f117ea047345e0dff8c to your computer and use it in GitHub Desktop.
Possible Vue Compostion Memoizer
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
/** | |
* Vue Composition HigherOrderFunction (HOF) that will take a Vue Composition function and return a | |
* memoized version of that function. | |
* | |
* A Memoized function in a function that, when given the same inputs, will skip any calculations done by the function | |
* and instead return a cached result that matches the inputs. This allows us to skip any duplicate computation | |
* or other calculations that would occur by calling the function from multiple places but with the same inputs. | |
* | |
* It is expected that the resolver function will take the provided inputs and convert them to a keyable value | |
* that can be used in the internal cache. | |
* | |
* @example | |
* function useStringValue(lengthLimit) { | |
* const val = ref("") | |
* const setVal = newVal => { | |
* if (newVal.length > lengthLimit) | |
* throw new Error("cannot set length longer than " + lengthLimit) | |
* | |
* val.value = newVal | |
* } | |
* | |
* return { | |
* val, | |
* setVal | |
* } | |
* } | |
* | |
* const memoizedFunction = createMemoizedComposable(useStringValue, (lengthLimit) => lengthLimit) | |
* | |
* const { val: valOne, setVal: setValOne } = memoizedFunction(20) | |
* const { val: valTwo, setVal: setValTwo } = memoizedFunction(10) | |
* const { val: valThree, setVal: setValThree } = memoizedFunction(20) | |
* | |
* // valOne and valThree are the _exact same reference_ of a Ref<string> | |
* // valTwo is a different Ref<string> because the arguments passed were different | |
* | |
* @template T | |
* | |
* @param {function(...*): T} composable | |
* @param {function(...*): *} resolver | |
* @returns {function(...*): T} | |
*/ | |
export function createMemoizedComposable(composable, resolver) { | |
assert(composable, "Argument 'composable' is required") | |
assert(composable, "Argument 'resolver' is required") | |
const cache = new Map() | |
return function createSingletonScope(...args) { | |
let subscribers = 0 | |
//If there is already a shared composable state for the provided args, return it | |
const cacheKey = resolver(...args) | |
if (cache.has(cacheKey)) { | |
subscribers++ | |
return cache.get(cacheKey) | |
} | |
//If not, setup an effectScope so that we can manage the reactivity setup/teardown | |
// of this shared composable separate from the setup/teardown of the system using it. | |
//Run the composable inside the effect scope to capture the reactive state that we want | |
// to be able to shared between many systems. | |
let scope = effectScope(true) | |
const state = scope.run(() => composable(...args)) | |
//Set the reactive state into the cache for future callers | |
cache.set(cacheKey, state) | |
//When any system using this shared composable disposes (for components, unmount), decrement our subscriber count. | |
//If there are no more subscribers to this variant of the shared composable, kill it off and remove from the cache. | |
onScopeDispose(() => { | |
if (scope && --subscribers <= 0) { | |
scope.stop() | |
scope = null | |
cache.delete(cacheKey) | |
} | |
}) | |
//Return the reactive state of the shared composable | |
//This only happens if the cache is cold for the set of args provided | |
return state | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment