v5.0.0-alpha.1 #610
markerikson
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This alpha release adds two experimental new memoizers with different capabilities and tradeoffs.
Changelog
New Experimental
autotrack
andweakmap
MemoizersReselect has always allowed swapping out the function memoizer used inside of
createSelector
. Reselect's existingdefaultMemoize
memoizer is based on shallow equality checks for arguments. This is simple and fast, but also has limitations.The most common limitation with
defaultMemoize
and shallow equality checks is that it can produce "false positive" recalculations. A classic example of this would be a selector that extracts an array of todo IDs:If you dispatch a
todoToggled()
action that flipsstate.todos[3].completed
, that will produce a newtodo
object at index 3 and a newtodos
array, because it's an immutable update. However,selectTodoIds
will see thattodos
is a new reference and recalculate the result, even though none of thetodo.id
fields have changed. This creates a new IDs array that is shallow-equal to the last one. This is both a waste of computation time, and a new result reference that could cause a component to re-render even though the array hasn't conceptually changed.createSelector
has also always defaulted to a cache size of 1. With Reselect 4.1, we added amaxSize
option todefaultMemoize
, but this requires a known fixed cache size value at creation time. It's hard to estimate how many cache entries you might need in the future (will my list have 10 items? 100? 1000?).This release includes two new experimental memoizers that have differing tradeoffs, with the goal of addressing these issues in different ways.
For now, both of these can be used by calling
createSelectorCreator
and generating a customized version ofcreateSelector
:In future 5.0-alpha releases, we'd like to investigate passing these directly to
createSelector()
calls.autotrackMemoize
autotrackMemoize
uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team. It uses aProxy
to wrap arguments and track accesses to nested fields in your selector on first read. Later, when the selector is called with new arguments, it identifies which accessed fields have changed and only recalculates the result if one or more of those accessed fields have changed.This allows it to be more precise than the shallow equality checks in
defaultMemoize
. In fact, with that exact sameselectTodoIds
code above, a selector that usesautotrackMemoize
will not recalculate if you flip atodo.completed
field, because it can see that you only accessed thetodo.id
fields.This memoizer is directly based on the code and concepts from these articles and examples:
Design Tradeoffs for
autotrackMemoize
defaultMemoize
, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)createSelector(state => state.todos, todos => todos)
that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check. (You shouldn't write selectors like that to begin with :) But we've seen them in the wild.)defaultMemoize
will, which may also result in fewer component re-rendersUse Cases for
autotrackMemoize
autotrackMemoize
is likely best used for cases where you need to access specific nested fields in data, and avoid recalculating if other fields in the same data objects are immutably updated.weakmapMemoize
defaultMemoize
has to be explicitly configured to have a cache size larger than 1, and uses an LRU cache internally.weakmapMemoize
creates a tree ofWeakMap
-based cache nodes based on the identity of the arguments it's been called with (in this case, the extracted values from your input functions). This allowsweakmapMemoize
to have an effectively infinite cache size. Cache results will be kept in memory as long as references to the arguments still exist, and then cleared out as the arguments are garbage-collected.This memoizer is directly based on code from the React codebase:
packages/react/src/ReactCache.js
Design Tradeoffs for
weakmapMemoize
This memoizer is likely best used for cases where you need to call the same selector instance with many different arguments, such as a single selector instance that is used in a list item component and called with item IDs like
useSelector(state => selectSomeData(state, props.category))
.Argument Memoization Uses
defaultMemoize
Back in PR #297, the outer argument memoization was changed to use the same provided memoization function as the inner extracted values memoization. For performance reasons, we've flipped this back to use
defaultMemoize
and shallow equality checks for the outer argument memoization. In most cases this should have no change at all for end users, because the memoizer is rarely overridden anyway.We'd like to investigate allowing customization of both arguments and extracted values memoizers in a later 5.0-alpha release
What's Changed
Full Changelog: v5.0.0-alpha.0...v5.0.0-alpha.1
This discussion was created from the release v5.0.0-alpha.1.
Beta Was this translation helpful? Give feedback.
All reactions