You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
const customSelectorCreator = createSelectorCreator(
customMemoize, // function to be used to memoize resultFunc,记忆resultFunc
option1, // option1 will be passed as second argument to customMemoize 第二个惨呼
option2, // option2 will be passed as third argument to customMemoize 第三个参数
option3 // option3 will be passed as fourth argument to customMemoize 第四个参数
)
const customSelector = customSelectorCreator(
input1,
input2,
resultFunc // resultFunc will be passed as first argument to customMemoize 作为第一个参数传递给customMomize
)
复制代码
const mySelectorA = state => state.a
const mySelectorB = state => state.b
// The result function in the following selector
// is simply building an object from the input selectors 由selectors构建的一个对象
const structuredSelector = createSelector(
mySelectorA,
mySelectorB,
mySelectorC,
(a, b, c) => ({
a,
b,
c
})
)
复制代码
const isFirstTodoCompleteSelector = createSelector(
state => state.todos[0],
todo => todo && todo.completed
)
复制代码
下面的 state 更新函数和isFirstTodoCompleteSelector将不会正常工作工作:
export default function todos(state = initialState, action) {
switch (action.type) {
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
// BAD: mutating an existing object
return state.map(todo => {
todo.completed = !areAllMarked
return todo
})
default:
return state
}
}
复制代码
下面的 state 更新函数和isFirstTodoComplete一起可以正常工作.
export default function todos(state = initialState, action) {
switch (action.type) {
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
// GOOD: returning a new object each time with Object.assign
return state.map(todo => Object.assign({}, todo, {
completed: !areAllMarked
}))
default:
return state
}
}
复制代码
// tests for the first three selectors...
test("firstSelector unit test", () => { ... })
test("secondSelector unit test", () => { ... })
test("thirdSelector unit test", () => { ... })
// We have already tested the previous
// three selector outputs so we can just call `.resultFunc`
// with the values we want to test directly:
test("myComposedSelector unit test", () => {
// here instead of calling selector()
// we just call selector.resultFunc()
assert(selector.resultFunc(1, 2, 3), true)
assert(selector.resultFunc(2, 2, 1), false)
})
复制代码
Selector 可以计算衍生的数据, 可以让 Redux 做到存储尽可能少的 state。
Selector 比较高效, 只有在某个参数发生变化的时候才发生计算过程.
Selector 是可以组合的, 他们可以作为输入, 传递到其他的 selector.
//这个例子不必太在意,后面会有详细的介绍
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
export const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax })
)
let exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.20 },
{ name: 'orange', value: 0.95 },
]
}
}
console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState)) // 0.172
console.log(totalSelector(exampleState)) // { total: 2.322 }
复制代码
Table of Contents
Installation
实例
API
createSelector
defaultMemoize
createSelectorCreator
createStructuredSelector
FAQ
Related Projects
License
安装
npm install reselect
实例
缓存 Selcectos 的动机
containers/VisibleTodoList.js
在上面的例子中,
mapStateToProps
调用getVisibleTodos
去计算todos
. 这个函数设计的是相当好的, 但是有个缺点:todos
在每一次组件更新的时候都会重新计算. 如果 state 树的结构比较大, 或者计算比较昂贵, 每一次组件更新的时候都进行计算的话, 将会导致性能问题.Reselect
能够帮助 redux 避免不必要的计算过程.创建一个缓存 Selector
我们可以使用记忆缓存 selector 代替
getVisibleTodos
, 如果state.todos
和state.visibilityFilter
发生变化, 他会重新计算state
, 但是只发生在其他部分的 state 变化, 就不会重新计算.Reslect 提供一个函数
createSelector
来创建一个记忆 selectors.createSelector
接受input-selectors
和一个变换函数作为参数. 如果 Redux 的 state 发生改变造成input-selector
的值发生改变, selector 会调用变换函数, 依据input-selector
做参数, 返回一个结果. 如果input-selector
返回的结果和前面的一样, 那么就会直接返回有关 state, 会省略变换函数的调用.下面我们定义一个记忆 selector
getVisibleTodos
替代非记忆的版本selectors/index.js
上面的的实例中,
getVisibilityfilter
和getTodos
是 input-selectors. 这两个函数是普通的非记忆 selector 函数, 因为他们没有变换他们 select 的数据.getVisibleTodos
另一方面是一个记忆 selector. 他接收getVisibilityfilter
和getTodos
作为 input-selectors, 并且作为一个变换函数计算筛选的 todo list.组合 selectors
一个记忆性 selector 本身也可以作为另一个记忆性 selector 的 input-selector. 这里
getVisibleTodos
可以作为 input-selector 作为关键字筛选的 input-selector:把 Selector 连接到 Redux Store
如果你正在使用 React Redux, 你可以 直接在
mapStateToProps()
中调用 selector:containers/VisibleTodoList.js
在 Selectors 中获取 React 的 props
目前为止, 我们仅仅看到 selectors 接收 store 的 state 作为一个参数, 其实一个 selector 叶可以接受 props.
这里是一个
App
组件, 渲染出三个VisibleTodoList
组件, 每一个组件有ListId
属性.components/App.js
每一个
VisibleTodoList
container 应该根据各自的listId
属性获取 state 的不同部分. 所以我们修改一下getVisibilityFilter
和getTodos
, 便于接受一个属性参数selectors/todoSelectors.js
props
可以从mapStateToProps
传递到getVisibleTodos
:现在
getVisibleTodos
可以获取props
, 每一部分似乎都工作的不错.**但是还有个问题! 当
getVisibleTodos
selector 和VisibleTodoList
container 的多个实例一起工作的时候, 记忆功能就不能正常运行:containers/VisibleTodoList.js
使用
createSelector
创建的 selector 时候, 如果他的参数集合和上一次的参数机会是一样的, 仅仅返回缓存的值. 如果我们交替渲染<VisibleTodoList listId="1" />
和<VisibleTodoList listId="2" />
时, 共享的 selector 将会交替接受{listId:1}
和{listId:2}
作为他的 props 的参数. 这将会导致每一次调用的时候的参数都不同, 因此 selector 每次都会重新来计算而不是返回缓存的值. 下一部分我们将会介绍怎么解决这个问题.跨越多个组件使用 selectors 共享 props
在多个
VisibleTodoList
组件中共享 selector, 同时还要保持记忆性, 每一个组件的实例需要他们自己的 selector 私有拷贝.现在让我们创建一个函数
makeGetVisibleTodos
, 这个函数每次调用的时候返回一个新的getVisibleTodos
的拷贝:selectors/todoSelectors.js
我们也需要设置给每一个组件的实例他们各自获取私有的 selector 方法.
mapStateToProps
的connect
函数可以帮助完成这个功能.如果
mapStateToProps
提供给connect
的不是一个对形象, 而是一个函数, 每个container
中就会创建独立的mapStateToProps
实例.在下面的实例中,
mapStateProps
创建一个新的getVisibleTodos
selector, 他返回一个mapStateToProps
函数, 这个函数能够接入新的 selector.如果我们把
makeMapStateToprops
传递到connect
, 每一个visibleTodoList
container 将会获得各自的含有私有getVisibleTodos
selector 的mapStateToProps
函数. 这样一来记忆就正常了, 不管VisibleTodoList
containers 的渲染顺序怎么样.containers/VisibleTodoList.js
API
createSelector(…inputSelectors|[inputSelectors],resultFunc)
接受一个或者多个 selectors, 或者一个 selectors 数组, 计算他们的值并且作为参数传递给
resultFunc
.createSelector
通过判断 input-selector 之前调用和之后调用的返回值的全等于 (===, 这个地方英文文献叫 reference equality, 引用等于, 这个单词是本质, 中文没有翻译出来). 经过createSelector
创建的 selector 应该是 immutable(不变的).经过
createSelector
创建的 Selectors 有一个缓存, 大小是 1. 这意味着当一个 input-selector 变化的时候, 他们总是会重新计算 state, 因为 Selector 仅仅存储每一个 input-selector 前一个值.在 selector 内部获取一个组件的 props 非常有用. 当一个 selector 通过
connect
函数连接到一个组件上, 组件的属性作为第二个参数传递给 selector:defaultMemoize(func, equalityCheck = defaultEqualityCheck)
defaultMemoize
能记住通过 func 传递的参数. 这是createSelector
使用的记忆函数.defaultMemoize
通过调用equalityCheck
函数来决定一个参数是否已经发生改变. 因为defaultMemoize
设计出来就是和 immutable 数据一起使用, 默认的equalityCheck
使用引用全等于来判断变化:defaultMemoize
和createSelectorCreator
去配置equalityCheck
函数.createSelectorCreator(memoize,…memoizeOptions)
createSelectorCreator
用来配置定制版本的createSelector
.memoize
参数是一个有记忆功能的函数, 来代替defaultMemoize
.…memoizeOption
展开的参数是 0 或者更多的配置选项, 这些参数传递给memoizeFunc
.selectorsresultFunc
作为第一个参数传递给memoize
,memoizeOptions
作为第二个参数:在
customSelecotr
内部滴啊用 memoize 的函数的代码如下:下面是几个可能会用到的
createSelectorCreator
的实例:为
defaultMemoize
配置equalityCheck
使用 loadsh 的 memoize 函数来缓存未绑定的缓存.
createStructuredSelector({inputSelectors}, selectorCreator = createSelector)
如果在普通的模式下使用
createStructuredSelector
函数可以提升便利性. 传递到connect
的 selector 装饰者 (这是 js 设计模式的概念, 可以参考相关的书籍) 接受他的 input-selectors, 并且在一个对象内映射到一个键上.createStructuredSelector
接受一个对象, 这个对象的属性是 input-selectors, 函数返回一个结构性的 selector. 这个结构性的 selector 返回一个对象, 对象的键和inputSelectors
的参数是相同的, 但是使用 selectors 代替了其中的值.结构性的 selectors 可以是嵌套式的:
FAQ
Q: 为什么当输入的 state 发生改变的时候, selector 不重新计算?
A: 检查一下你的记忆韩式是不是和你的 state 更新函数相兼容 (例如: 如果你正在使用 Redux). 例如: 使用
createSelector
创建的 selector 总是创建一个新的对象, 原来期待的是更新一个已经存在的对象.createSelector
使用 (===) 检测输入是否改变, 因此如果改变一个已经存在的对象没有触发 selector 重新计算的原因是改变一个对象的时候没有触发相关的检测. 提示:如果你正在使用 Redux, 改变一个 state 对象的错误可能有.下面的实例定义了一个 selector 可以决定数组的第一个 todo 项目是不是已经被完成:
下面的 state 更新函数和
isFirstTodoCompleteSelector
将不会正常工作工作:下面的 state 更新函数和
isFirstTodoComplete
一起可以正常工作.如果你没有使用 Redux, 但是有使用 mutable 数据的需求, 你可以使用
createSelectorCreator
代替默认的记忆函数, 并且使用不同的等值检测函数. 请参看这里 和 这里作为参考.Q: 为什么 input state 没有改变的时候, selector 还是会重新计算?
A: 检查一下你的记忆函数和你你的 state 更新函数是不是兼容 (如果是使用 Redux 的时候, 看看 reducer). 例如: 使用每一次更新的时候, 不管值是不是发生改变,
createSelector
创建的 selector 总是会收到一个新的对象.createSelector
函数使用 (===
) 检测 input 的变化, 由此可知如果每次都返回一个新对象, 表示 selector 总是在每次更新的时候重新计算.下面的 selector 在每一次 REMOVE_OLD 调用的时候, 都会重新计算, 因为 Array.filter 总是返回一个新对象. 但是在大多数情况下, REMOVE_OLD action 都不会改变 todo 列表, 所以重新计算是不必要的.
你可以通过 state 更新函数返回一个新对象来减少不必要的重计算操作, 这个对象执行深度等值检测, 只有深度不相同的时候才返回新对象.
替代的方法是, 在 selector 中使用深度检测方法替代默认的
equalityCheck
函数:检查
equalityCheck
函数的更替或者在 state 更新函数中做深度检测并不总是比重计算的花销小. 如果每次重计算的花销总是比较小, 可能的原因是 Reselect 没有通过connect
函数传递mapStateProps
单纯对象的原因.Q: 没有 Redux 的情况下可以使用 Reselect 吗?
A: 可以. Reselect 没有其他任何的依赖包, 因此尽管他设计的和 Redux 比较搭配, 但是独立使用也是可以的. 目前的版本在传统的 Flux APP 下使用是比较成功的.
Q: 怎么才能创建一个接收参数的 selector.
A:Reselect 没有支持创建接收参数的 selectors, 但是这里有一些实现类似函数功能的建议.
如果参数不是动态的, 你可以使用工厂函数:
总的达成共识看这里和超越 neclear-js是: 如果一个 selector 需要动态的参数, 那么参数应该是 store 中的 state. 如果你决定好了在应用中使用动态参数, 像下面这样返回一个记忆函数是比较合适的:
Q:默认的记忆函数不太好, 我能用个其他的吗?
A: 我认为这个记忆韩式工作的还可以, 但是如果你需要一个其他的韩式也是可以的. 可以看看这个例子
Q: 怎么才能测试一个 selector?
A: 对于一个给定的 input, 一个 selector 总是产出相同的结果. 基于这个原因, 做单元测试是非常简单的.
在 state 更新函数调用的时候同时检测 selector 的记忆函数的功能也是非常有用的 (例如 使用 Redux 的时候检查 reducer). 每一个 selector 都有一个
recomputations
方法返回重新计算的次数:另外, selectors 保留了最后一个函数调用结果的引用, 这个引用作为
.resultFunc
. 如果你已经聚合了其他的 selectors, 这个函数引用可以帮助你测试每一个 selector, 不需要从 state 中解耦测试.例如如果你的 selectors 集合像下面这样:
selectors.js
单元测试就像下面这样:
test/selectors.js
最后, 每一个 selector 有一个
resetRecomputations
方法, 重置 recomputations 方法为 0, 这个参数的意图是在面对复杂的 selector 的时候, 需要很多独立的测试, 你不需要管理复杂的手工计算, 或者为每一个测试创建” 傻瓜”selector.Q:Reselect 怎么和 Immutble.js 一起使用?
A:
creatSelector
创建的 Selectors 应该可以和 Immutable.js 数据结构一起完美的工作. 如果你的 selector 正在重计算, 并且你认为 state 没有发生变化, 一定要确保知道哪一个 Immutable.js 更新方法, 这个方法只要一更新总是返回新对象. 哪一个方法只有集合实际发生变化的时候才返回新对象.如果一个操作导致的 selector 更新总是返回一个新对象, 可能会发生不必要的重计算.看这里. 这是一个关于 pros 的讨论, 使用深全等于来检测例如
immutable.js
来减少不必要的重计算过程.Q: 可以在多个组件之间共享 selector 吗?
A: 使用
createSelector
创建的 Selector 的缓存的大小只有 1. 这个设定使得多个组件的实例之间的参数不同, 跨组件共享 selector 变得不合适. 这里也有几种办法来解决这个问题:Q: 有 TypeScript 的类型吗?
A: 是的!他们包含在
package.json
里. 可以很好的工作.Q:怎么构建一个柯里化selector?
A:尝试一些这里助手函数, 由MattSPalmer提供
有关的项目
reselect-map
因为 Reselect 不可能保证缓存你所有的需求, 在做非常昂贵的计算的时候, 这个方法比较有用. 查看一下 reselect-maps readme
reselect-map 的优化措施仅仅使用在一些小的案例中, 如果你不确定是不是需要他, 就不要使用它.
License
MIT
https://juejin.cn/post/6844903829381595150
The text was updated successfully, but these errors were encountered: