Skip to main content

Redux 常见问题解答:设计决策

¥Redux FAQ: Design Decisions

目录

¥Table of Contents

设计决策

¥Design Decisions

为什么 Redux 不将状态和操作传递给订阅者?

¥Why doesn't Redux pass the state and action to subscribers?

订阅者旨在响应状态值本身,而不是操作。状态更新是同步处理的,但向订阅者的通知可以批量或去抖动,这意味着订阅者并不总是收到每个操作的通知。这是一个常见的 性能优化,以避免重复重新渲染。

¥Subscribers are intended to respond to the state value itself, not the action. Updates to the state are processed synchronously, but notifications to subscribers can be batched or debounced, meaning that subscribers are not always notified with every action. This is a common performance optimization to avoid repeated re-rendering.

通过使用增强器覆盖 store.dispatch 来改变通知订阅者的方式,可以进行批处理或去抖动。此外,还有一些库将 Redux 更改为批量处理操作,以优化性能并避免重复重新渲染:

¥Batching or debouncing is possible by using enhancers to override store.dispatch to change the way that subscribers are notified. Also, there are libraries that change Redux to process actions in batches to optimize performance and avoid repeated re-rendering:

  • redux-batch 允许仅通过一个通知将一系列操作传递给 store.dispatch()

    ¥redux-batch allows passing an array of actions to store.dispatch() with only one notification,

  • redux-batched-subscribe 允许批量订阅因调度而发生的通知。

    ¥redux-batched-subscribe allows batching of subscribe notifications that occur as a result of dispatches.

预期的保证是 Redux 最终会调用具有最新可用状态的所有订阅者,但并不总是为每个操作调用每个订阅者。只需调用 store.getState(),订阅者即可获得存储状态。如果不破坏操作的批处理方式,则无法在订阅者中提供该操作。

¥The intended guarantee is that Redux eventually calls all subscribers with the most recent state available, but not that it always calls each subscriber for each action. The store state is available in the subscriber simply by calling store.getState(). The action cannot be made available in the subscribers without breaking the way that actions might be batched.

在订阅者内部使用操作(这是一项不受支持的功能)的一个潜在用例是确保组件仅在某些类型的操作之后重新渲染。相反,应该通过以下方式控制重新渲染:

¥A potential use-case for using the action inside a subscriber -- which is an unsupported feature -- is to ensure that a component only re-renders after certain kinds of actions. Instead, re-rendering should be controlled through:

  1. shouldComponentUpdate 生命周期方法

    ¥the shouldComponentUpdate lifecycle method

  2. 虚拟 DOM 相等性检查 (vDOMEq)

    ¥the virtual DOM equality check (vDOMEq)

  3. React.PureComponent

  4. 使用 React-Redux:使用 mapStateToProps 仅将组件订阅到它们需要的存储部分。

    ¥Using React-Redux: use mapStateToProps to subscribe components to only the parts of the store that they need.

更多信息

¥Further Information

文章

¥Articles

讨论

¥Discussions

为什么 Redux 不支持使用 actions 和 reducer 类?

¥Why doesn't Redux support using classes for actions and reducers?

对于具有大量面向对象编程经验的程序员来说,使用称为操作创建者的函数来返回操作对象的模式似乎违反直觉,他们会认为这是类和实例的强大用例。不支持操作对象和化简器的类实例,因为类实例使序列化和反序列化变得棘手。像 JSON.parse(string) 这样的反序列化方法将返回一个普通的旧 Javascript 对象而不是类实例。

¥The pattern of using functions, called action creators, to return action objects may seem counterintuitive to programmers with a lot of Object Oriented Programming experience, who would see this is a strong use-case for Classes and instances. Class instances for action objects and reducers are not supported because class instances make serialization and deserialization tricky. Deserialization methods like JSON.parse(string) will return a plain old Javascript object rather than class instances.

存储常见问题解答 中所述,如果你对持久性和时间旅行调试等未按预期工作的情况感到满意,欢迎你将不可序列化的项目放入 Redux 存储中。

¥As described in the Store FAQ, if you are okay with things like persistence and time-travel debugging not working as intended, you are welcome to put non-serializable items into your Redux store.

序列化使浏览器能够以更少的内存存储已分派的所有操作以及之前的存储状态。回溯和 '热重载' 存储是 Redux 开发者体验和 Redux DevTools 功能的核心。这还使得反序列化的操作能够存储在服务器上,并在使用 Redux 进行服务器端渲染的情况下在浏览器中重新序列化。

¥Serialization enables the browser to store all actions that have been dispatched, as well as the previous store states, with much less memory. Rewinding and 'hot reloading' the store is central to the Redux developer experience and the function of Redux DevTools. This also enables deserialized actions to be stored on the server and re-serialized in the browser in the case of server-side rendering with Redux.

更多信息

¥Further Information

文章

¥Articles

讨论

¥Discussions

为什么中间件签名使用柯里化?

¥Why does the middleware signature use currying?

Redux 中间件是使用类似于 const middleware = storeAPI => next => action => {} 的三重嵌套函数结构编写的,而不是类似于 const middleware = (storeAPI, next, action) => {} 的单个函数。这有几个原因。

¥Redux middleware are written using a triply-nested function structure that looks like const middleware = storeAPI => next => action => {}, rather than a single function that looks like const middleware = (storeAPI, next, action) => {}. There's a few reasons for this.

一是 "currying" 函数是一种标准的函数式编程技术,Redux 明确打算在其设计中使用函数式编程原则。另一个是柯里化函数创建闭包,你可以在其中声明在中间件的生命周期中存在的变量(可以将其视为与在类实例的生命周期中存在的实例变量的功能等效)。最后,这只是 Redux 最初设计时选择的方法。

¥One is that "currying" functions is a standard functional programming technique, and Redux was explicitly intended to use functional programming principles in its design. Another is that currying functions creates closures where you can declare variables that exist for the lifetime of the middleware (which could be considered a functional equivalent to instance variables that exist for the lifetime of a class instance). Finally, it's simply the approach that was chosen when Redux was initially designed.

声明中间件的 柯里化函数签名 对某些人来说是 认为没有必要,因为在执行 applyMiddleware 函数时 store 和 next 都可用。该问题已确定不是 值得引入突破性变化,因为 Redux 生态系统中现在有数百个中间件依赖于现有的中间件定义。

¥The curried function signature of declaring middleware is deemed unnecessary by some, because both store and next are available when the applyMiddleware function is executed. This issue has been determined to not be worth introducing breaking changes, as there are now hundreds of middleware in the Redux ecosystem that rely on the existing middleware definition.

更多信息

¥Further Information

讨论

¥Discussions

为什么 applyMiddlewaredispatch 使用闭包?

¥Why does applyMiddleware use a closure for dispatch?

applyMiddleware 从存储中获取现有的调度并关闭它以创建初始中间件链,这些中间件已使用公开 getState 和调度函数的对象调用,这使得 初始化期间依赖调度 能够运行中间件。

¥applyMiddleware takes the existing dispatch from the store and closes over it to create the initial chain of middlewares that have been invoked with an object that exposes the getState and dispatch functions, which enables middlewares that rely on dispatch during initialization to run.

更多信息

¥Further Information

讨论

¥Discussions

  • 为什么 applyMiddleware 使用闭包进行调度?

    ¥Why does applyMiddleware use a closure for dispatch?

为什么 combineReducers 在调用每个 reducer 时不包含带有整个状态的第三个参数?

¥Why doesn't combineReducers include a third argument with the entire state when it calls each reducer?

combineReducers 有态度地鼓励按字段分割 reducer 逻辑。如 超越 combineReducers 中所述,combineReducers 被故意限制为处理单个常见用例:通过将更新每个状态切片的工作委托给特定的切片缩减器来更新作为普通 Javascript 对象的状态树。

¥combineReducers is opinionated to encourage splitting reducer logic by domain. As stated in Beyond combineReducers,combineReducers is deliberately limited to handle a single common use case: updating a state tree that is a plain Javascript object by delegating the work of updating each slice of state to a specific slice reducer.

每个 reducer 的潜在第三个参数应该是什么并不是立即显而易见的:整个状态树、一些回调函数、状态树的其他部分等。如果 combineReducers 不适合你的用例,请考虑使用 combineSectionReducersreduceReducers 等库来实现具有深度嵌套的 reducer 和需要访问全局状态的 reducer 的其他选项。

¥It's not immediately obvious what a potential third argument to each reducer should be: the entire state tree, some callback function, some other part of the state tree, etc. If combineReducers doesn't fit your use case, consider using libraries like combineSectionReducers or reduceReducers for other options with deeply nested reducers and reducers that require access to the global state.

如果已发布的实用程序都不能解决你的用例,你始终可以自己编写一个函数来完全满足你的需要。

¥If none of the published utilities solve your use case, you can always write a function yourself that does just exactly what you need.

更多信息

¥Further information

文章

¥Articles

讨论

¥Discussions

为什么 mapDispatchToProps 不允许使用 getState()mapStateToProps() 的返回值?

¥Why doesn't mapDispatchToProps allow use of return values from getState() or mapStateToProps()?

有人请求在 mapDispatch 内部使用整个 statemapState 的返回值,以便当在 mapDispatch 内部声明函数时,它们可以关闭存储中的最新返回值。

¥There have been requests to use either the entire state or the return value of mapState inside of mapDispatch, so that when functions are declared inside of mapDispatch, they can close over the latest returned values from the store.

mapDispatch 不支持此方法,因为这意味着每次更新存储时也会调用 mapDispatch。这将导致每次状态更新时都重新创建函数,从而增加大量性能开销。

¥This approach is not supported in mapDispatch because it would mean also calling mapDispatch every time the store is updated. This would cause the re-creation of functions with every state update, thus adding a lot of performance overhead.

处理此用例的首选方法(需要根据当前状态和 mapDispatchToProps 函数更改 props)是使用 mergeProps(connect 函数的第三个参数)。如果指定,则会传递 mapStateToProps()mapDispatchToProps() 和容器组件的 props 的结果。从 mergeProps 返回的普通对象将作为 props 传递给封装的组件。

¥The preferred way to handle this use-case--needing to alter props based on the current state and mapDispatchToProps functions--is to work from mergeProps, the third argument to the connect function. If specified, it is passed the result of mapStateToProps(), mapDispatchToProps(), and the container component's props. The plain object returned from mergeProps will be passed as props to the wrapped component.

更多信息

¥Further information

讨论

¥Discussions