仅在开发环境进行稳定性检查
Reselect在开发模式中包含额外的检查,以帮助捕获并警告有关选择器行为的错误。
inputStabilityCheck
由于"Cascading Memoization"在Reselect中的工 作方式,您的input selectors不应在每次运行时返回新的引用。如果一个输入选择器总是返回一个新的引用,比如
state => ({ a: state.a, b: state.b })
或者
state => state.todos.map(todo => todo.id)
那将导致选择器永远无法正确地进行记忆化。
由于这是一个常见的错误,我们添加了一个开发模式检查来捕获这个问题。默认情况下,createSelector
现在会在选择器第一次被调用时运行两次input selectors。如果对相同的调用结果看起来不同,它将记录警告,包括参数和两个不同的提取输入值集。
type DevModeCheckFrequency = 'always' | 'once' | 'never'
可能的值 | 描述 |
---|---|
once | 只在选择器第一次被调用时运行。 |
always | 每次选择器被调用时都运行。 |
never | 从不运行输入稳定性检查。 |
输入稳定性检查在生产环境中自动禁用。
您可以通过两种方式配置此行为:
1. 通过setGlobalDevModeChecks
全局设置:
setGlobalDevModeChecks
函数从Reselect导出,应该用所需的设置调用它。
import { setGlobalDevModeChecks } from 'reselect'
// 只在选择器第一次被调用时运行。 (默认)
setGlobalDevModeChecks({ inputStabilityCheck: 'once' })
// 每次选择器被调用时都运行。
setGlobalDevModeChecks({ inputStabilityCheck: 'always' })
// 从不运行输入稳定性检查。
setGlobalDevModeChecks({ inputStabilityCheck: 'never' })
2. 通过直接向createSelector
传递inputStabilityCheck
选项,对每个选择器进行设置:
- TypeScript
- JavaScript
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
// 创建一个选择器,每次运行时都会对输入选择器的结果进行双重检查。
const selectCompletedTodosLength = createSelector(
[
// ❌ 错误的使用案例:此输入选择器将无法
// 正确地进行记忆化,因为它总是返回一个新的引用。
(state: RootState) =>
state.todos.filter(({ completed }) => completed === true)
],
completedTodos => completedTodos.length,
// 将覆盖全局设置。
{ devModeChecks: { inputStabilityCheck: 'always' } }
)
import { createSelector } from 'reselect'
// 创建一个选择器,每次运行时都会对输入选择器的结果进行双重检查。
const selectCompletedTodosLength = createSelector(
[
// ❌ 错误的使用案例:此输入选择器将无法
// 正确地进行记忆化,因为它总是返回一个新的引用。
state => state.todos.filter(({ completed }) => completed === true)
],
completedTodos => completedTodos.length,
// 将覆盖全局设置。
{ devModeChecks: { inputStabilityCheck: 'always' } }
)
这将覆盖通过调用setGlobalDevModeChecks
设置的全局输入稳定性检查。
identityFunctionCheck
在使用 Reselect 时,坚持关于提取逻辑和转换逻辑之间关注点分离的基本哲学是至关重要的。
-
提取逻辑:这指的是像
state => state.todos
这样的操作,应该放在 [输入选择器] 中。提取逻辑负责从更广泛的状态或数据集中检索或'选择'数据。 -
转换逻辑:相反,转换逻辑,如
todos => todos.map(({ id }) => id)
,应该放在 [结果函数] 中。这是你操作、格式化或转换输入选择器提取的数据的地方。
最重要的是,Reselect 中有效的记忆化依赖于遵循这些指导原 则。只有当提取和转换逻辑正确地分离时,记忆化才能正确地工作。通过将提取逻辑保留在输入选择器中,将转换逻辑保留在结果函数中,Reselect 可以有效地确定何时重用缓存结果,何时重新计算它们。这不仅提高了性能,而且确保了你的选择器的一致性和可预测性。
为了使记忆化按预期工作,必须遵循这两个指导原则。如果任何一个被忽视,记忆化将无法正常工作。考虑以下例子以便清晰理解:
// ❌ 错误的使用案例:这将无法正确地记忆化,而且没有任何用处!
const brokenSelector = createSelector(
// ✔️ 好的:包含提取逻辑。
[(state: RootState) => state.todos],
// ❌ 坏的:不包含转换逻辑。
todos => todos
)
type DevModeCheckFrequency = 'always' | 'once' | 'never'
可能的值 | 描述 |
---|---|
once | 只在第一次调用选择器时运行。 |
always | 每次调用选择器时都运行。 |
never | 从不运行身份函数检查。 |
身份函数检查在生产环境中会自动禁用。
你可以通过两种方式配置这种行为:
1. 通过 setGlobalDevModeChecks
全局配置:
import { setGlobalDevModeChecks } from 'reselect'
// 只在第一次调用选择器时运行。 (默认)
setGlobalDevModeChecks({ identityFunctionCheck: 'once' })
// 每次调用选择器时都运行。
setGlobalDevModeChecks({ identityFunctionCheck: 'always' })
// 从不运行身份函数检查。
setGlobalDevModeChecks({ identityFunctionCheck: 'never' })
2. 通过直接向 createSelector
传递 identityFunctionCheck
选项来对每个选择器进行配置:
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
// 创建一个选择器,检查结果函数是否是一个身份函数。
const selectTodos = createSelector(
// ✔️ 好的:包含提取逻辑。
[(state: RootState) => state.todos],
// ❌ 坏的:不包含转换逻辑。
todos => todos,
// 将覆盖全局设置。
{ devModeChecks: { identityFunctionCheck: 'always' } }
)
这将覆盖通过调用 setGlobalDevModeChecks
设置的全局身份函数检查。