Update createSelector and defaultMemoize to accept options (maxSize, equalityCheck, resultEqualityCheck)#513
Conversation
Codecov Report
@@ Coverage Diff @@
## master #513 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 1 2 +1
Lines 53 94 +41
Branches 7 21 +14
=========================================
+ Hits 53 94 +41
|
4c07b61 to
89ff857
Compare
| (...args: SelectorResultArray<Selectors>) => Result | ||
| ] | ||
| ...items: | ||
| | [...Selectors, (...args: SelectorResultArray<Selectors>) => Result] |
There was a problem hiding this comment.
💬 this is so stupid and ugly, but it works (when an optional object at the end of the tuple didn't)
There was a problem hiding this comment.
Whoaaa. Don't recall ever seeing : | [ in that order before.
There was a problem hiding this comment.
Hah, yeah. My first attempt was just throwing an optional object into the tuple, but that totally broke everything.
I just sort of randomly thought "what if I make it a union of two exact tuples instead", and to my shock and amazement that actually worked. Immediately.
There was a problem hiding this comment.
I really don't need to bother leaving this comment, but for anyone curious, :\n| ...\n| ... is how Typescript does multi-line type-or expressions, just prefix each line with a |. It's like trailing commas in a list.
|
Also just remembered that extracting another file will break the Babel changes I made in the last couple days. Have to compile that file too. |
erikras
left a comment
There was a problem hiding this comment.
It kills me to see all those anys in there. But if you're using a single cache structure for multiple Entrys, I guess you just can't know.
Glad to see some of this old code (that still has 44k downloads/month) getting a new lease on life. 👏
| return false | ||
| } | ||
|
|
||
| // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible. |
| (...args: SelectorResultArray<Selectors>) => Result | ||
| ] | ||
| ...items: | ||
| | [...Selectors, (...args: SelectorResultArray<Selectors>) => Result] |
There was a problem hiding this comment.
Whoaaa. Don't recall ever seeing : | [ in that order before.
|
Yeah, upon further thought I think I can switch most or all of the The amusing thing is I just copy-pasted the code, and all I had to do was whip up a pair of (helps that the types were really simple in the first place of course, but still neat) |
createSelector now accepts an options object as its last argument.
For now, the only field in that object is `memoizeOptions`. This
field represents whatever the additional parameters are for the
supplied memoization function.
If this options object is supplied, `createSelector`will try to
use the `memoizeOptions` field as the params to `memoize()`. If
the object exists but no `memoizeOptions` field, it will fall back
to whatever memoizer options were passed as args to the original
`createSelectorCreator` call.
Further complicating things: normally, `memoizeOptions` is an array
containing all the individual args. For example, `defaultMemoize`
currently takes one options arg: the `equalityCheck` function used
for comparisons. So, to pass that directly here, you'd do:
createSelector(
[input1, input2],
output,
{ memoizeOptions: [ equalityCheck ] }
)
But, it's very common to want to _only_ use the _first_ options
arg, and many libs have _only_ one arg anyway (like defaultMemoize
does right now). So, to simplify usage, we also allow passing
that first arg directly in `memoizeOptions` without an array
around it:
createSelector(
[input1, input2],
output,
{ memoizeOptions: equalityCheck }
)
Internally, this is done by checking to see if `memoizeOptions` is
an array or not. If it's an array, we use it as-is. If not, we
assume it must be "the first options arg" and wrap it in an array
so it can be spread via `apply()`.
This does mean that a lib that takes an array as its first options
arg will break things here, but I can't immediately see any libs
that do that - most either take `equalityCheck` or an object.
Besides, the goal of this effort is that `defaultMemoize` will
handle enough use cases that's all you'd typically use anyway.
Implementation based on Erik Rasmussen's `lru-memoize`: https://github.com/erikras/lru-memoize
89ff857 to
ee1500d
Compare
|
Replaced a bunch of Also renamed |
| }, | ||
| "dependencies": {} | ||
| "dependencies": { | ||
| "memoize-one": "^6.0.0-beta.1", |
This PR:
createSelectortypes to accept an optional options object as the last argumentmemoizerOptionsfield that exactly matches the type of all of the providedmemoizefunction's arguments after the initial function-to-be-memoized, with exact type inference of all argumentsmemoizerOptionsfield to optionally be just the first of the memoize function's additional arguments, to support the most common cases of configuring a memoize function (such as passing a custom equality comparison function, or whatever options object the memoization lib supports)defaultMemoizeto its own filedefaultMemoizeto accept either of anequalityCheckfunction or an options object as its first argumentdefaultMemoizeto add support for a customizably-sized cache with LRU behavior, based on https://github.com/erikras/lru-memoizeresultCheckEqualitycomparison function to allow reusing the most recent cached value that matches a recalculated value (the "todos.map(todo => todo.id)is still shallow equal after toggling a todo" use case)This makes the following usages possible:
These changes should solve a majority of the complaints users have had with Reselect:
createSelectorCreatorjust to customize the equality comparisonAnd all of this should be entirely backwards-compatible with existing code :)
Example of the latter case from the
defaultMemoizetests: