Redux 常见问题解答:设计决策
目录
- 为什么 Redux 不将状态和操作传递给订阅者?
- 为什么 Redux 不支持使用类来创建操作和 reducers?
- 为什么中间件签名使用柯里化?
- 为什么 applyMiddleware 使用闭包来处理 dispatch?
- 为什么
combineReducers
在调用每个 reducer 时不包含包含整个状态的第三个参数? - 为什么 mapDispatchToProps 不允许使用
getState()
或mapStateToProps()
的返回值?
设计决策
为什么 Redux 不将状态和操作传递给订阅者?
订阅者旨在响应状态值本身,而不是操作。对状态的更新是同步处理的,但对订阅者的通知可以进行批处理或去抖动,这意味着订阅者并不总是收到每个操作的通知。这是一种常见的 性能优化,可以避免重复渲染。
通过使用增强器来覆盖 store.dispatch
以更改通知订阅者的方式,可以实现批处理或去抖动。此外,还有一些库可以更改 Redux 以批处理操作,以优化性能并避免重复渲染
- redux-batch 允许将操作数组传递给
store.dispatch()
,并且只进行一次通知, - redux-batched-subscribe 允许对由于调度而发生的订阅通知进行批处理。
预期保证是 Redux 最终会使用最新的可用状态调用所有订阅者,但并非总是为每个操作调用每个订阅者。订阅者可以通过简单地调用 store.getState()
来获取存储状态。在不破坏操作批处理方式的情况下,无法在订阅者中提供操作。
在订阅者中使用操作的潜在用例(这是一个不受支持的功能)是确保组件仅在某些类型的操作后重新渲染。相反,重新渲染应通过以下方式控制:
- the shouldComponentUpdate 生命周期方法
- the 虚拟 DOM 等效性检查 (vDOMEq)
- React.PureComponent
- 使用 React-Redux:使用 mapStateToProps 将组件订阅到他们需要的存储部分。
更多信息
文章
讨论
为什么 Redux 不支持使用类来创建操作和 reducer?
使用函数(称为操作创建者)来返回操作对象的模式可能对具有大量面向对象编程经验的程序员来说似乎违反直觉,他们会认为这是类和实例的强大用例。不支持操作对象和 reducer 的类实例,因为类实例会使序列化和反序列化变得很麻烦。像 JSON.parse(string)
这样的反序列化方法将返回一个普通的 JavaScript 对象,而不是类实例。
如 存储常见问题解答 中所述,如果您不介意持久性和时间旅行调试等功能无法按预期工作,欢迎您将不可序列化项放入 Redux 存储中。
序列化使浏览器能够以更少的内存存储所有已分派的 action 以及之前的 store 状态。重绕和“热重载”store 是 Redux 开发者体验和 Redux DevTools 功能的核心。这还使序列化后的 action 能够存储在服务器上,并在使用 Redux 进行服务器端渲染的情况下在浏览器中重新序列化。
更多信息
文章
讨论
为什么中间件签名使用柯里化?
Redux 中间件使用三层嵌套函数结构编写,看起来像 const middleware = storeAPI => next => action => {}
,而不是看起来像 const middleware = (storeAPI, next, action) => {}
的单个函数。这有几个原因。
一个原因是“柯里化”函数是一种标准的函数式编程技术,Redux 在设计时明确地打算使用函数式编程原则。另一个原因是柯里化函数会创建闭包,您可以在其中声明在中间件生命周期内存在的变量(这可以被认为是与在类实例生命周期内存在的实例变量等效的函数式方法)。最后,这只是 Redux 最初设计时选择的方法。
声明中间件的 柯里化函数签名 被一些人认为是 不必要的,因为当执行 applyMiddleware 函数时,store 和 next 都可用。这个问题已被确定为 不值得引入重大更改,因为现在 Redux 生态系统中存在数百个依赖于现有中间件定义的中间件。
更多信息
讨论
为什么applyMiddleware
使用闭包来处理dispatch
?
applyMiddleware
获取来自存储的现有dispatch
,并将其封闭起来以创建最初的中间件链,这些中间件已使用一个对象调用,该对象公开了getState
和dispatch
函数,这使得在初始化期间依赖于dispatch
的中间件能够运行。
更多信息
讨论
为什么combineReducers
在调用每个 reducer 时不包含一个包含整个状态的第三个参数?
combineReducers
倾向于鼓励按领域拆分 reducer 逻辑。如超越 combineReducers
中所述,combineReducers
故意限制为处理一个常见的用例:通过将更新每个状态切片的任务委托给特定的切片 reducer 来更新一个简单的 JavaScript 对象形式的状态树。
每个 reducer 的潜在第三个参数并不明显:整个状态树、某个回调函数、状态树的某个其他部分等等。如果 combineReducers
不适合您的用例,请考虑使用像combineSectionReducers 或 reduceReducers 这样的库,它们提供了其他选项,可以处理深度嵌套的 reducer 以及需要访问全局状态的 reducer。
如果已发布的实用程序都没有解决您的用例,您始终可以自己编写一个函数来满足您的特定需求。
更多信息
文章
讨论
为什么 mapDispatchToProps
不允许使用 getState()
或 mapStateToProps()
的返回值?
曾有人提出请求,希望在 mapDispatch
中使用整个 state
或 mapState
的返回值,以便在 mapDispatch
中声明函数时,它们可以闭包最新从 store 返回的值。
这种方法在 mapDispatch
中不受支持,因为它意味着每次 store 更新时也要调用 mapDispatch
。这会导致每次状态更新时重新创建函数,从而增加大量性能开销。
处理这种情况(需要根据当前状态和 mapDispatchToProps 函数修改 props)的首选方法是使用 mergeProps
,它是 connect
函数的第三个参数。如果指定了 mergeProps
,它将接收 mapStateToProps()
、mapDispatchToProps()
和容器组件的 props 的结果。从 mergeProps
返回的普通对象将作为 props 传递给包装的组件。
更多信息
讨论