Enhance Reselect selectors with deeper memoization and cache management.
翻译准备中
开发工具TypeScriptGitHub @toomuchdesign自行安装运营者未确认
试用过了吗? 点一下就好,无需登录。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
安装型项目无需部署域名也可验证。个人仓库可通过 GitHub 登录验证,组织仓库可使用上方的仓库验证文件。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗From v5, reselect provides the ability to natively implement custom memoization/caching solutions via createSelector options. Most of the features re-reselect used to enable should be now natively available in reselect. re-reselect will try to support reselect v5+ for backward compatibility reasons.
re-reselect is a lightweight wrapper around Reselect meant to enhance selectors with deeper memoization and cache management.
Switching between different arguments using standard reselect selectors causes cache invalidation since default reselect cache has a limit of one.
re-reselect forwards different calls to different reselect selectors stored in cache, so that computed/memoized values are retained.
re-reselect selectors work as normal reselect selectors but they are able to determine when creating a new selector or querying a cached one on the fly, depending on the supplied arguments.
Useful to:
reselect with custom caching strategiesimport {createCachedSelector} from 're-reselect';
// Normal reselect routine: declare "inputSelectors" and "resultFunc"
const getUsers = state => state.users;
const getLibraryId = (state, libraryName) => state.libraries[libraryName].id;
const getUsersByLibrary = createCachedSelector(
// inputSelectors
getUsers,
getLibraryId,
// resultFunc
(users, libraryId) => expensiveComputation(users, libraryId),
)(
// re-reselect keySelector (receives selectors' arguments)
// Use "libraryName" as cacheKey
(_state_, libraryName) => libraryName
);
// Cached selectors behave like normal selectors:
// 2 reselect selectors are created, called and cached
const reactUsers = getUsersByLibrary(state, 'react');
const vueUsers = getUsersByLibrary(state, 'vue');
// This 3rd call hits the cache
const reactUsersAgain = getUsersByLibrary(state, 'react');
// reactUsers === reactUsersAgain
// "expensiveComputation" called twice in total
npm install reselect
npm install re-reselect
Let's say getData is a reselect selector.
getData(state, itemId, 'dataA');
getData(state, itemId, 'dataB');
getData(state, itemId, 'dataA');
The 3rd argument invalidates reselect cache on each call, forcing getData to re-evaluate and return a new value.
re-reselect selectors keep a cache of reselect selectors stored by cacheKey.
cacheKey is the return value of the keySelector function. It's by default a string or number but it can be anything depending on the chosen cache strategy (see cache objects docs).
keySelector is a custom function which:
state, itemId, dataType)cacheKeyA unique persisting reselect selector instance stored in cache is used to compute data for a given cacheKey (1:1).
Back to the example, we might setup re-reselect to retrieve data by querying one of the cached selectors using the 3rd argument as cacheKey, allowing cache invalidation only when state or itemId change (but not dataType):
const getData = createCachedSelector(
state => state,
(state, itemId) => itemId,
(state, itemId, dataType) => dataType,
(state, itemId, dataType) => expensiveComputation(state, itemId, dataType)
)(
(state, itemId, dataType) => dataType // Use dataType as cacheKey
);
Replacing a selector with a cached selector is invisible to the consuming application since the API is the same.
When a cached selector is called, the following happens behind the scenes:
cacheKey for the current call by executing keySelectorreselect selector stored under the given cacheKeyEasy, but doesn't scale. See "join similar selectors" example.
makeGetPieceOfData selector factory as explained in Reselect docsThe solution suggested in Reselect docs is fine, but it has a few downsides:
get selectors and makeGet selector factoriesmakeGetPieceOfData selector factory into a memoizer function and call the returning memoized selectorThis is what re-reselect actually does. 😀
Given your reselect selectors:
import { createSelector } from 'reselect';
export const getMyData = createSelector(
selectorA,
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
);
...add keySelector in the second function call:
import { createCachedSelector } from 're-reselect';
export const getMyData = createCachedSelector(
selectorA,
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
)(
(state, arg1, arg2) => arg2, // Use arg2 as cacheKey
);
Voilà, getMyData is ready for use!
const myData = getMyData(state, 'foo', 'bar');
A few good examples and a bonus:
// Basic usage: use a single argument as cacheKey
createCachedSelector(
// ...
)(
(state, arg1, arg2, arg3) => arg3
)
// Use multiple arguments and chain them into a string
createCachedSelector(
// ...
)(
(state, arg1, arg2, arg3) => `${arg1}:${arg3}`
)
// Extract properties from an object
createCachedSelector(
// ...
)(
(state, props) => `${props.a}:${props.b}`
)
Use a cacheObject which provides that feature by supplying a cacheObject option.
You can also write your own cache strategy!
This example shows how re-reselect would solve the scenario described in reselect docs.
Like a normal reselect selector!
re-reselect selectors expose the same reselect testing methods:
dependenciesresultFuncrecomputationsresetRecomputationsRead more about testing selectors on reselect docs.
reselect selectors stored in the cacheEach re-reselect selector exposes a getMatchingSelector method which returns the underlying matching selector instance for the given arguments, instead of the result.
getMatchingSelector expects the same arguments as a normal selector call BUT returns the instance of the cached selector itself.
Once you get a selector instance you can call its public methods.
import {createCachedSelector} from 're-reselect';
export const getMyData = createCachedSelector(selectorA, selectorB, (A, B) =>
doSomethingWith(A, B)
)(
(state, arg1) => arg1 // cacheKey
);
// Call your selector
const myFooData = getMyData(state, 'foo');
const myBarData = getMyData(state, 'bar');
// Call getMatchingSelector method to retrieve underlying reselect selectors
// which generated "myFooData" and "myBarData" results
const myFooDataSelector = getMyData.getMatchingSelector(state, 'foo');
const myBarDataSelector = getMyData.getMatchingSelector(state, 'bar');
// Call reselect's selectors methods
myFooDataSelector.recomputations();
myFooDataSelector.resetRecomputations();
import {createCachedSelector} from 're-reselect';
createCachedSelector(
// ...reselect's `createSelector` arguments
)(
keySelector | { options }
)
Takes the same arguments as reselect's createSelector and returns a new function which accepts a keySelector or an options object.
Returns a selector instance.
Creates a pre-typed version of createCachedSelector with the state type baked in, mirroring reselect's createSelector.withTypes. Set the state type once instead of annotating it on every input selector:
const createAppCachedSelector = createCachedSelector.withTypes<RootState>();
Without withTypes you annotate state on every input selector:
const selectTodoById = createCachedSelector(
(state: RootState) => state.todos,
(state: RootState, id: string) => id,
(todos, id) => todos[id],
)((state: RootState, id) => id);
With a pre-typed creator, state is inferred everywhere while per-selector params and the result stay inferred:
const selectTodoById = createAppCachedSelector(
[
(state) => state.todos, // `state` inferred as `RootState`
(state, id: string) => id, // `id` param still inferred
],
(todos, id) => todos[id],
)((state, id) => id); // `keySelector` `state` inferred too
Pass input selectors as an array. As with reselect's
createSelector.withTypes, state inference only works when input selectors are given as a single array argument. With the variadic form (input selectors spread as separate arguments),stateand the combiner arguments are inferred asany.
Only the state type is pre-typed. withTypes is a type-only helper: at runtime it returns the same createCachedSelector unchanged. Calls can be chained to narrow the state further (the override type must extend the current one).
import {createStructuredCachedSelector} from 're-reselect';
createStructuredCachedSelector(
// ...reselect's `createStructuredSelector` arguments
)(
keySelector | { options }
)
Takes the same arguments as reselect's createStructuredSelector and returns a new function which accepts a keySelector or an options object.
Returns a selector instance.
A custom function receiving the same arguments as your selectors (and inputSelectors) and returning a cacheKey.
cacheKey is by default a string or number but can be anything depending on the chosen cache strategy (see cacheObject option).
The keySelector idea comes from Lodash's .memoize resolver.
| Option | Type | Default | Description |
|---|---|---|---|
keySelector | function | undefined | The keySelector used by the cached selector. |
cacheObject | object | FlatObjectCache | An optional custom cache strategy object to handle the caching behaviour. Read more about re-reselect's custom cache here. |
keySelectorCreator | function | undefined | An optional function returning the keySelector used by the cached selector. Overrides any provided keySelector. |
selectorCreator | function | reselect's createSelector | An optional function describing a custom version of createSelector. |
type keySelectorCreator = (selectorInputs: {
inputSelectors: InputSelector[];
resultFunc: ResultFunc;
// Absent unless a `keySelector` was also supplied
keySelector?: KeySelector;
}) => KeySelector;
This allows the ability to dynamically generate keySelectors on runtime based on provided inputSelectors/resultFunc supporting key selectors composition. It overrides any provided keySelector.
See programmatic keySelector composition example.
createCachedSelector and createStructuredCachedSelector return a selector instance which extends the API of a standard reselect selector.
The following are advanced methods and you won't need them for basic usage!
| Member | Description |
|---|---|
selector.getMatchingSelector(selectorArguments) | Retrieve the selector responding to the given arguments, or undefined if none is cached. |
selector.removeMatchingSelector(selectorArguments) | Remove from the cache the selector responding to the given arguments. |
selector.cache | Get the cacheObject instance being used by the selector (for advanced caching operations like this). |
selector.clearCache() | Clear whole selector cache. |
selector.keySelector | Get keySelector for utility compositions or testing. |
| Member | Description |
|---|---|
selector.dependencies | Get an array containing the provided inputSelectors. Refer to relevant discussion on Reselect repo. |
selector.resultFunc | Get resultFunc for easily testing composed selectors. |
selector.recomputations() | Return the number of times the selector's result function has been recomputed. |
selector.resetRecomputations() | Reset recomputations count. |
v6 is a rewrite in TypeScript. JavaScript users only need the runtime changes. The changelog has the full migration table.
Runtime and packaging:
resetRecomputations() returns undefined. Read the count with recomputations().keySelector throws when the selector is created, not on its first call.cacheObject.get() counts as a cache miss.https://esm.sh/re-reselect, or bundle re-reselect. Deep imports like re-reselect/dist/... no longer resolve.import reReselect from 're-reselect' fails: use named imports or import * as.module entry). Transpile node_modules if you target older engines.TypeScript:
createCachedSelector<S, R1, T>(…) fail with TS2558. Drop them and annotate the input selectors, or pass <[typeof selectA, typeof selectB], Result>.keySelector reading an argument no input selector declares. Add a parametric input selector such as (state, id) => id for that argument.state annotation infers state as any, as in reselect v5. Annotate it, or use withTypes with an array of input selectors.getMatchingSelector is typed as returning OutputSelector | undefined, as it already did at runtime on a cache miss. Check the result or use ?..keySelectorCreator receives keySelector as optional. Check it before calling it.Selector and OutputSelector are no longer exported: import them from reselect. CreateSelectorInstance is gone too: use reselect's CreateSelectorFunction. The Parametric* types are gone: write ParametricSelector<S, P, R> as Selector<S, R, [P]>.OutputCachedSelector<InputSelectors, Result> now types the cached selector, not the factory returned by createCachedSelector(...). Annotate the selector instead.KeySelectorCreator<InputSelectors, Result> takes the input selectors instead of the state type.createStructuredCachedSelector call with a very large selectors object (roughly 50+ keys) can fail with TS2589. Split it into smaller ones.