使用选择器派生数据
- 为什么好的 Redux 架构会将状态保持在最小限度并派生其他数据
- 使用选择器函数推导数据和封装查找的原则
- 如何使用 Reselect 库编写记忆化的选择器以进行优化
- 使用 Reselect 的高级技巧
- 用于创建选择器的其他工具和库
- 编写选择器的最佳实践
推导数据
我们特别建议 Redux 应用程序应该保持 Redux 状态最小化,并在可能的情况下从该状态推导出附加值.
这包括计算过滤后的列表或汇总值。例如,一个待办事项应用程序会在状态中保留一个原始的待办事项对象列表,但在状态更新时,会从状态外部推导出一个过滤后的待办事项列表。类似地,可以从存储外部计算所有待办事项是否已完成或剩余的待办事项数量。
这有几个好处
- 实际状态更容易阅读
- 计算这些附加值并使其与其他数据保持同步所需的逻辑更少
- 原始状态仍然存在作为参考,并没有被替换
这对于 React 状态也是一个很好的原则!很多时候,用户尝试定义一个useEffect
钩子,它等待状态值改变,然后用一些派生值设置状态,比如setAllCompleted(allCompleted)
。相反,可以在渲染过程中直接推导出该值并直接使用,而无需将该值保存到状态中。
function TodoList() {
const [todos, setTodos] = useState([])
// Derive the data while rendering
const allTodosCompleted = todos.every(todo => todo.completed)
// render with this value
}
使用选择器计算派生数据
在一个典型的 Redux 应用程序中,推导数据的逻辑通常被写成我们称为选择器的函数。
选择器主要用于封装从状态中查找特定值的逻辑,推导值的实际逻辑,以及通过避免不必要的重新计算来提高性能。
您不需要对所有状态查找都使用选择器,但它们是一种标准模式,被广泛使用。
基本选择器概念
“选择器函数”是任何接受 Redux 存储状态(或状态的一部分)作为参数并返回基于该状态的数据的函数。
选择器不需要使用特殊库来编写,并且使用箭头函数或function
关键字编写都没有关系。例如,以下都是有效的选择器函数
// Arrow function, direct lookup
const selectEntities = state => state.entities
// Function declaration, mapping over an array to derive values
function selectItemIds(state) {
return state.items.map(item => item.id)
}
// Function declaration, encapsulating a deep lookup
function selectSomeSpecificField(state) {
return state.some.deeply.nested.field
}
// Arrow function, deriving values from an array
const selectItemsWhoseNamesStartWith = (items, namePrefix) =>
items.filter(item => item.name.startsWith(namePrefix))
选择器函数可以取任何你想要的名称。但是,我们建议在选择器函数名称前加上select
,并加上对所选值的描述。典型的例子包括selectTodoById
、selectFilteredTodos
和selectVisibleTodos
。
如果你使用过React-Redux 中的useSelector
钩子,你可能已经熟悉了选择器函数的基本概念 - 我们传递给useSelector
的函数必须是选择器
function TodoList() {
// This anonymous arrow function is a selector!
const todos = useSelector(state => state.todos)
}
选择器函数通常在 Redux 应用程序的两个不同部分定义
- 在切片文件中,与 reducer 逻辑一起
- 在组件文件中,在组件外部或在
useSelector
调用中内联
选择器函数可以在任何你可以访问整个 Redux 根状态值的地方使用。这包括useSelector
钩子、connect
的mapState
函数、中间件、thunk 和 saga。例如,thunk 和中间件可以访问getState
参数,因此你可以在那里调用选择器
function addTodosIfAllowed(todoText) {
return (dispatch, getState) => {
const state = getState()
const canAddTodos = selectCanAddTodos(state)
if (canAddTodos) {
dispatch(todoAdded(todoText))
}
}
}
通常不可能在 reducer 内部使用选择器,因为切片 reducer 只能访问 Redux 状态的自身切片,而大多数选择器期望被赋予整个 Redux 根状态作为参数。
使用选择器封装状态形状
使用选择器函数的第一个原因是在处理 Redux 状态形状时进行封装和重用。
假设你的一个useSelector
钩子对 Redux 状态的一部分进行了非常具体的查找
const data = useSelector(state => state.some.deeply.nested.field)
这确实是合法的代码,可以正常运行。但从架构角度来看,可能不是最佳方案。想象一下,你有多个组件需要访问该字段。如果需要更改该状态片段的存储位置,会发生什么?你将不得不更改所有引用该值的useSelector
钩子。因此,与我们建议使用 action creators 来封装创建 actions 的细节类似,我们建议定义可重用的 selectors 来封装给定状态片段的存储位置信息。然后,你可以在代码库中的任何地方使用给定的 selector 函数,只要你的应用程序需要检索该特定数据即可。
理想情况下,只有你的 reducer 函数和 selectors 应该知道确切的状态结构,这样如果更改了某个状态的存储位置,你只需要更新这两个逻辑部分。.
因此,通常将可重用的 selectors 直接定义在 slice 文件中是一个好主意,而不是总是将它们定义在组件内部。
对 selectors 的一种常见描述是,它们就像“对你的状态进行查询”。你并不关心查询是如何获得你所需数据的,你只关心你请求了数据并获得了结果。
使用记忆化优化 selectors
Selector 函数通常需要执行相对“昂贵”的计算,或者创建新的对象和数组引用的派生值。出于多种原因,这可能会影响应用程序的性能。
- 与
useSelector
或mapState
一起使用的 selectors 将在每次分派 action 后重新运行,无论 Redux 根状态的哪个部分实际更新。当输入状态部分没有改变时重新运行昂贵的计算是浪费 CPU 时间,而且大多数情况下输入很可能不会改变。 useSelector
和mapState
依赖于返回值的===
引用相等性检查来确定组件是否需要重新渲染。如果 selector始终返回新的引用,即使派生数据与上次实际上相同,它也会强制组件重新渲染。这在使用map()
和filter()
等数组操作时尤其常见,这些操作会返回新的数组引用。
例如,这个组件写得不好,因为它的useSelector
调用始终返回一个新的数组引用。这意味着组件将在每次分派 action 后重新渲染,即使输入state.todos
切片没有改变。
function TodoList() {
// ❌ WARNING: this _always_ returns a new reference, so it will _always_ re-render!
const completedTodos = useSelector(state =>
state.todos.map(todo => todo.completed)
)
}
另一个例子是,一个组件需要做一些“昂贵”的工作来转换数据。
function ExampleComplexComponent() {
const data = useSelector(state => {
const initialData = state.data
const filteredData = expensiveFiltering(initialData)
const sortedData = expensiveSorting(filteredData)
const transformedData = expensiveTransformation(sortedData)
return transformedData
})
}
类似地,这种“昂贵”的逻辑将在每次分派操作后重新运行。它不仅可能创建新的引用,而且如果state.data
实际上没有改变,这项工作就不需要做。
因此,我们需要一种方法来编写优化的选择器,以避免在传递相同的输入时重新计算结果。这就是记忆化的概念。
记忆化是一种缓存形式。它涉及跟踪函数的输入,并存储输入和结果以供以后参考。如果一个函数被调用时与之前相同的输入,该函数可以跳过实际工作,并返回上次接收这些输入值时生成的相同结果。这通过仅在输入发生变化时才进行工作,并在输入相同的情况下始终返回相同的結果引用来优化性能。
接下来,我们将看看编写记忆化选择器的一些选项。
使用 Reselect 编写记忆化选择器
Redux 生态系统传统上使用一个名为 Reselect 的库来创建记忆化选择器函数。还有其他类似的库,以及 Reselect 的多种变体和包装器——我们将在后面讨论这些。
createSelector
概述
Reselect 提供一个名为 createSelector
的函数来生成记忆化选择器。createSelector
接受一个或多个“输入选择器”函数,以及一个“输出选择器”函数,并返回一个新的选择器函数供您使用。
createSelector
作为 我们官方的 Redux Toolkit 包 的一部分包含在内,并重新导出以方便使用。
createSelector
可以接受多个输入选择器,这些选择器可以作为单独的参数或数组提供。所有输入选择器返回的结果将作为单独的参数提供给输出选择器。
const selectA = state => state.a
const selectB = state => state.b
const selectC = state => state.c
const selectABC = createSelector([selectA, selectB, selectC], (a, b, c) => {
// do something with a, b, and c, and return a result
return a + b + c
})
// Call the selector function and get a result
const abc = selectABC(state)
// could also be written as separate arguments, and works exactly the same
const selectABC2 = createSelector(selectA, selectB, selectC, (a, b, c) => {
// do something with a, b, and c, and return a result
return a + b + c
})
当您调用选择器时,Reselect 将使用您提供的所有参数运行您的输入选择器,并查看返回的值。如果任何结果与之前===
不同,它将重新运行输出选择器,并将这些结果作为参数传递。如果所有结果都与上次相同,它将跳过重新运行输出选择器,并直接返回之前缓存的最终结果。
这意味着“输入选择器”通常应该只提取和返回值,而“输出选择器”应该执行转换工作。
一个比较常见的错误是编写一个“输入选择器”来提取值或进行一些推导,以及一个“输出选择器”来简单地返回其结果。
// ❌ BROKEN: this will not memoize correctly, and does nothing useful!
const brokenSelector = createSelector(
state => state.todos,
todos => todos
)
任何只返回其输入的“输出选择器”都是错误的!输出选择器应该始终包含转换逻辑。
类似地,一个记忆化选择器绝不应该使用state => state
作为输入!这将强制选择器始终重新计算。
在典型的 Reselect 使用中,您将顶级“输入选择器”编写为普通函数,并使用createSelector
来创建记忆化选择器,这些选择器查找嵌套的值。
const state = {
a: {
first: 5
},
b: 10
}
const selectA = state => state.a
const selectB = state => state.b
const selectA1 = createSelector([selectA], a => a.first)
const selectResult = createSelector([selectA1, selectB], (a1, b) => {
console.log('Output selector running')
return a1 + b
})
const result = selectResult(state)
// Log: "Output selector running"
console.log(result)
// 15
const secondResult = selectResult(state)
// No log output
console.log(secondResult)
// 15
请注意,当我们第二次调用selectResult
时,“输出选择器”没有执行。因为selectA1
和selectB
的结果与第一次调用相同,所以selectResult
能够返回第一次调用的记忆化结果。
createSelector
行为
重要的是要注意,默认情况下,createSelector
只记忆最近的一组参数。这意味着如果您多次使用不同的输入调用选择器,它仍然会返回结果,但它必须不断重新运行输出选择器以生成结果。
const a = someSelector(state, 1) // first call, not memoized
const b = someSelector(state, 1) // same inputs, memoized
const c = someSelector(state, 2) // different inputs, not memoized
const d = someSelector(state, 1) // different inputs from last time, not memoized
此外,您可以将多个参数传递给选择器。Reselect 将使用这些确切的输入调用所有输入选择器。
const selectItems = state => state.items
const selectItemId = (state, itemId) => itemId
const selectItemById = createSelector(
[selectItems, selectItemId],
(items, itemId) => items[itemId]
)
const item = selectItemById(state, 42)
/*
Internally, Reselect does something like this:
const firstArg = selectItems(state, 42);
const secondArg = selectItemId(state, 42);
const result = outputSelector(firstArg, secondArg);
return result;
*/
因此,您提供的所有“输入选择器”都应该接受相同类型的参数。否则,选择器将失效。
const selectItems = state => state.items
// expects a number as the second argument
const selectItemId = (state, itemId) => itemId
// expects an object as the second argument
const selectOtherField = (state, someObject) => someObject.someField
const selectItemById = createSelector(
[selectItems, selectItemId, selectOtherField],
(items, itemId, someField) => items[itemId]
)
在这个例子中,selectItemId
期望它的第二个参数是一个简单值,而selectOtherField
期望第二个参数是一个对象。如果您调用selectItemById(state, 42)
,selectOtherField
将失效,因为它试图访问42.someField
。
Reselect 使用模式和限制
嵌套选择器
可以使用 createSelector
生成的选择器作为其他选择器的输入。在这个例子中,selectCompletedTodos
选择器被用作 selectCompletedTodoDescriptions
的输入。
const selectTodos = state => state.todos
const selectCompletedTodos = createSelector([selectTodos], todos =>
todos.filter(todo => todo.completed)
)
const selectCompletedTodoDescriptions = createSelector(
[selectCompletedTodos],
completedTodos => completedTodos.map(todo => todo.text)
)
传递输入参数
Reselect 生成的选择器函数可以接受任意数量的参数:selectThings(a, b, c, d, e)
。但是,重新运行输出的关键不在于参数的数量,也不在于参数本身是否已更改为新的引用。相反,它与定义的“输入选择器”以及它们的结果是否已更改有关。同样,"输出选择器" 的参数完全基于输入选择器返回的内容。
这意味着,如果要将其他参数传递给输出选择器,则必须定义输入选择器,这些选择器从原始选择器参数中提取这些值。
const selectItemsByCategory = createSelector(
[
// Usual first input - extract value from `state`
state => state.items,
// Take the second arg, `category`, and forward to the output selector
(state, category) => category
],
// Output selector gets (`items, category)` as args
(items, category) => items.filter(item => item.category === category)
)
然后,您可以像这样使用选择器。
const electronicItems = selectItemsByCategory(state, "electronics");
为了保持一致性,您可能需要考虑将其他参数作为单个对象传递给选择器,例如 selectThings(state, otherArgs)
,然后从 otherArgs
对象中提取值。
选择器工厂
createSelector
只有一个默认缓存大小为 1,并且每个选择器的唯一实例都是如此。 当单个选择器函数需要在多个地方使用不同的输入进行重用时,这会导致问题。
一种选择是创建一个“选择器工厂” - 一个运行 createSelector()
并每次调用时生成一个新的唯一选择器实例的函数。
const makeSelectItemsByCategory = () => {
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => category],
(items, category) => items.filter(item => item.category === category)
)
return selectItemsByCategory
}
当多个类似的 UI 组件需要根据 props 派生数据的不同子集时,这特别有用。
替代选择器库
虽然 Reselect 是 Redux 中使用最广泛的选择器库,但还有许多其他库可以解决类似的问题,或者扩展 Reselect 的功能。
proxy-memoize
proxy-memoize
是一个相对较新的记忆选择器库,它使用独特的实现方法。它依赖于 ES2015 Proxy
对象来跟踪对嵌套值的读取尝试,然后在以后的调用中仅比较嵌套值以查看它们是否已更改。在某些情况下,这可以提供比 Reselect 更好的结果。
一个很好的例子是,一个选择器可以派生出一个待办事项描述的数组。
import { createSelector } from 'reselect'
const selectTodoDescriptionsReselect = createSelector(
[state => state.todos],
todos => todos.map(todo => todo.text)
)
不幸的是,如果 `state.todos` 中的任何其他值发生变化,例如切换 `todo.completed` 标志,它将重新计算派生的数组。派生数组的 *内容* 是相同的,但由于输入 `todos` 数组发生了变化,它必须计算一个新的输出数组,并且该数组具有一个新的引用。
使用 `proxy-memoize` 的相同选择器可能看起来像这样
import { memoize } from 'proxy-memoize'
const selectTodoDescriptionsProxy = memoize(state =>
state.todos.map(todo => todo.text)
)
与 Reselect 不同,`proxy-memoize` 可以检测到只有 `todo.text` 字段被访问,并且只有在 `todo.text` 字段之一发生变化时才会重新计算其余部分。
它还具有内置的 `size` 选项,允许您为单个选择器实例设置所需的缓存大小。
它与 Reselect 相比有一些权衡和差异
- 所有值都作为单个对象参数传递。
- 它要求环境支持 ES2015 的 `Proxy` 对象(不支持 IE11)。
- 它更神奇,而 Reselect 更显式。
- 关于基于 `Proxy` 的跟踪行为,有一些边缘情况。
- 它更新,使用范围更小。
尽管如此,**我们正式鼓励考虑使用 `proxy-memoize` 作为 Reselect 的可行替代方案**。
re-reselect
https://github.com/toomuchdesign/re-reselect 通过允许您定义一个“键选择器”来改进 Reselect 的缓存行为。这用于在内部管理 Reselect 选择器的多个实例,这可以帮助简化跨多个组件的使用。
import { createCachedSelector } from 're-reselect'
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
)
reselect-tools
有时很难追踪多个 Reselect 选择器之间的关系,以及是什么导致选择器重新计算。 https://github.com/skortchmark9/reselect-tools 提供了一种跟踪选择器依赖关系的方法,以及它自己的 DevTools 来帮助可视化这些关系并检查选择器值。
redux-views
https://github.com/josepot/redux-views 与 re-reselect
类似,它提供了一种为每个项目选择唯一键的方法,以实现一致的缓存。它被设计为 Reselect 的近乎直接的替代品,实际上被提议作为 Reselect 版本 5 的一个选项。
Reselect v5 提案
我们在 Reselect 仓库中开启了一个路线图讨论,以确定 Reselect 未来版本中潜在的增强功能,例如改进 API 以更好地支持更大的缓存大小、用 TypeScript 重写代码库以及其他可能的改进。我们欢迎社区在该讨论中提供更多反馈。
在 React-Redux 中使用选择器
使用参数调用选择器
通常需要向选择器函数传递额外的参数。但是,useSelector
始终使用一个参数(Redux 根 state
)调用提供的选择器函数。
最简单的解决方案是向 useSelector
传递一个匿名选择器,然后立即使用 state
和任何其他参数调用实际的选择器。
import { selectTodoById } from './todosSlice'
function TodoListitem({ todoId }) {
// Captures `todoId` from scope, gets `state` as an arg, and forwards both
// to the actual selector function to extract the result
const todo = useSelector(state => selectTodoById(state, todoId))
}
创建唯一的选择器实例
在许多情况下,选择器函数需要在多个组件中重复使用。如果这些组件都使用不同的参数调用选择器,则会破坏记忆化 - 选择器永远不会连续多次看到相同的参数,因此永远无法返回缓存的值。
这里标准的方法是在组件中创建一个记忆化选择器的唯一实例,然后将其与 useSelector
一起使用。这允许每个组件始终将相同的参数传递给它自己的选择器实例,并且该选择器可以正确地记忆结果。
对于函数组件,这通常使用 useMemo
或 useCallback
完成。
import { makeSelectItemsByCategory } from './categoriesSlice'
function CategoryList({ category }) {
// Create a new memoized selector, for each component instance, on mount
const selectItemsByCategory = useMemo(makeSelectItemsByCategory, [])
const itemsByCategory = useSelector(state =>
selectItemsByCategory(state, category)
)
}
对于使用 connect
的类组件,这可以使用 mapState
的高级“工厂函数”语法完成。如果 mapState
函数在第一次调用时返回一个新函数,则该函数将用作实际的 mapState
函数。这提供了一个闭包,您可以在其中创建一个新的选择器实例。
import { makeSelectItemsByCategory } from './categoriesSlice'
const makeMapState = (state, ownProps) => {
// Closure - create a new unique selector instance here,
// and this will run once for every component instance
const selectItemsByCategory = makeSelectItemsByCategory()
const realMapState = (state, ownProps) => {
return {
itemsByCategory: selectItemsByCategory(state, ownProps.category)
}
}
// Returning a function here will tell `connect` to use it as
// `mapState` instead of the original one given to `connect`
return realMapState
}
export default connect(makeMapState)(CategoryList)
有效地使用选择器
虽然选择器是 Redux 应用程序中常见的模式,但它们经常被误用或误解。以下是一些正确使用选择器函数的指南。
在 Reducer 旁边定义选择器
选择器函数通常在 UI 层定义,直接在 useSelector
调用中定义。但是,这意味着在不同文件中定义的选择器之间可能存在重复,并且这些函数是匿名的。
像任何其他函数一样,您可以将匿名函数提取到组件外部以赋予其名称。
const selectTodos = state => state.todos
function TodoList() {
const todos = useSelector(selectTodos)
}
然而,应用程序的多个部分可能想要使用相同的查找。此外,从概念上讲,我们可能希望将todos
状态的组织方式的知识保留在todosSlice
文件中的实现细节中,以便将其集中在一个地方。
因此,**在相应的 reducer 旁边定义可重用的选择器是一个好主意**。在这种情况下,我们可以从todosSlice
文件导出selectTodos
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
todoAdded(state, action) {
state.push(action.payload)
}
}
})
export const { todoAdded } = todosSlice.actions
export default todosSlice.reducer
// Export a reusable selector here
export const selectTodos = state => state.todos
这样,如果我们碰巧对 todos 切片状态的结构进行了更新,相关的选择器就在这里,可以同时更新,对应用程序的其他部分的更改最小。
平衡选择器使用
有可能在应用程序中添加太多选择器。**为每个字段添加一个单独的选择器函数不是一个好主意!**这最终会将 Redux 变成类似于具有每个字段的 getter/setter 函数的 Java 类。它不会改进代码,而且可能会使代码更糟 - 维护所有这些额外的选择器需要大量额外的努力,并且将更难追踪哪些值在何处使用。
同样,**不要将每个选择器都进行记忆化!**。记忆化只有在你真正推导出结果并且推导出的结果每次都可能创建新的引用时才需要。**直接查找并返回值的 selector 函数应该是一个普通函数,而不是记忆化的**。
一些关于何时记忆化和何时不记忆化的示例
// ❌ DO NOT memoize: will always return a consistent reference
const selectTodos = state => state.todos
const selectNestedValue = state => state.some.deeply.nested.field
const selectTodoById = (state, todoId) => state.todos[todoId]
// ❌ DO NOT memoize: deriving data, but will return a consistent result
const selectItemsTotal = state => {
return state.items.reduce((result, item) => {
return result + item.total
}, 0)
}
const selectAllCompleted = state => state.todos.every(todo => todo.completed)
// ✅ SHOULD memoize: returns new references when called
const selectTodoDescriptions = state => state.todos.map(todo => todo.text)
根据需要重塑状态以适应组件
选择器不必局限于直接查找 - 它们可以在内部执行任何必要的转换逻辑。这对于帮助准备特定组件所需的数据特别有用。
Redux 状态通常以“原始”形式存储数据,因为状态应该保持最小化,并且许多组件可能需要以不同的方式呈现相同的数据。您可以使用选择器不仅提取状态,而且根据此特定组件的需要重塑它。这可能包括从根状态的多个切片中提取数据,提取特定值,将数据的不同部分合并在一起,或任何其他有帮助的转换。
如果组件也包含一些此类逻辑,那也没关系,但将所有这些转换逻辑提取到单独的选择器中以实现更好的重用和可测试性可能是有益的。
如果需要,全局化选择器
在编写切片 reducer 和选择器之间存在着固有的不平衡。切片 reducer 只知道其状态的一部分 - 对 reducer 而言,其 state
是唯一存在的,例如 todoSlice
中的 todo 数组。另一方面,选择器通常被编写为接受整个 Redux 根状态作为其参数。这意味着它们必须知道此切片数据在根状态中的位置,例如 state.todos
,即使这实际上是在创建根 reducer(通常在应用程序范围内的存储设置逻辑中)后才定义的。
一个典型的切片文件通常同时包含这两种模式。这很好,尤其是在小型或中型应用程序中。但是,根据您的应用程序架构,您可能希望进一步抽象选择器,以便它们不知道切片状态的位置 - 它必须传递给它们。
我们将这种模式称为“全局化”选择器。一个“全局化”选择器接受 Redux 根状态作为参数,并知道如何找到相关的状态切片以执行实际逻辑。一个“本地化”选择器期望只接受状态的一部分作为参数,而不知道或不关心它在根状态中的位置。
// "Globalized" - accepts root state, knows to find data at `state.todos`
const selectAllTodosCompletedGlobalized = state =>
state.todos.every(todo => todo.completed)
// "Localized" - only accepts `todos` as argument, doesn't know where that came from
const selectAllTodosCompletedLocalized = todos =>
todos.every(todo => todo.completed)
可以通过将“本地化”选择器包装在一个知道如何检索正确状态切片并将其传递下去的函数中,将它们转换为“全局化”选择器。
Redux Toolkit 的 createEntityAdapter
API 是这种模式的一个例子。如果您调用 todosAdapter.getSelectors()
,不带任何参数,它将返回一组期望实体切片状态作为其参数的“本地化”选择器。如果您调用 todosAdapter.getSelectors(state => state.todos)
,它将返回一组期望使用Redux 根状态作为其参数调用的“全局化”选择器。
拥有“本地化”版本的选择器也可能带来其他好处。例如,假设我们有一个高级场景,在存储中嵌套了多个 createEntityAdapter
数据副本,例如一个跟踪房间的 chatRoomsAdapter
,并且每个房间定义都包含一个 chatMessagesAdapter
状态来存储消息。我们不能直接查找每个房间的消息 - 我们首先必须检索房间对象,然后从该对象中选择消息。如果我们有一组用于消息的“本地化”选择器,这将更容易。
更多信息
- 选择器库
- Reselect: https://github.com/reduxjs/reselect
proxy-memoize
: https://github.com/dai-shi/proxy-memoizere-reselect
: https://github.com/toomuchdesign/re-reselectreselect-tools
: https://github.com/skortchmark9/reselect-toolsredux-views
: https://github.com/josepot/redux-views
- Reselect v5 路线图讨论:目标和 API 设计
- Randy Coulman 撰写了一系列关于选择器架构和 Redux 选择器全局化不同方法的博文,并分析了它们的权衡利弊。