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
Context providers 会比较提供的 value 的引用, 这个 value 可以是任意类型, 比如一个字符串, 一个对象, 但注意的是如果该 value 的引用发生改变, 所有 consumers 会 re-render, 即使该 consumer 只用到了 value 的一部分, 比如 value 对象的某个属性值.
该篇文章主要使用一个 demo, 针对使用传统
props&state
,context
,redux
做状态管理来进行性能优化.Demo 效果大致如下:
三种方法均尝试实现同一个效果, 有三个 room, 每个 room 是一个组件, 可以通过点击按钮改变背景颜色, 并且原则上改变其中一个 room 的背景颜色时其他 room 不会被重新渲染, 可以通过 console 里的输出查看.
完整代码如下: https://stackblitz.com/edit/react-tqmejp
一些基本总结
首先放一些有关 React 渲染和 Context 的总结:
this.setState()
,this.forceUpdate()
, hooks 组件的useState
的 setters,useReducer
的 dispatches 都会触发该组件的 re-renderReact.memo()
可以跳过一些无必要渲染, 如果该组件的 props 和上一次对比没有改变React.memo()
包裹, 这样即使 context value 发生了改变导致 re-render, 或者因为 React 本身递归式的渲染, 这些被包裹的组件可能能避免 re-render更详细的说明和总结可以查看这两篇文章:
Props & State
先看最简单的使用 props 和 state 实现的 demo:
虽然功能上实现了, 但是存在性能问题, 每当点击其中一个 room 的 flip 按钮, 其余 room 组件一样会被重新渲染, 具体效果大致如下:
原因也很简单, 每次点击按钮触发
flipLight
方法, 都会触发父组件PropsStateDemo
里的setLights
, lights 状态改变. 由于默认行为是父组件被渲染时, 子组件也会默认被渲染. 因此所有 Room 组件都被渲染了一次优化也很简单,
Room
组件的 props 分别是 isLit, flipLight, index. 每一个 Room 组件的 isLit 都应该是独立的, 也就是说当某个 Room 改变了 isLit 状态, 虽然这会导致 lights 状态变更, 且确实无法避免, 因为 lights 状态是一个数组, 但单个的 isLit 状态是独立的, 别的 Room 的 isLit 状态是不会影响到其他 Room 的. 同理 index 也是独立的. 而 flipLight 这个函数每次在父组件渲染的时候都会被传一份新的引用, 那尝试保证引用不变就行了.所以只要用
React.memo()
包裹Room
组件, 同时使用useCallback
保证每次flipLight
引用一样即可优化后的代码:
效果即是开头贴的示例效果, 就不重复放了.
Context
虽然这里完全没有必要使用到 Context, 但为了演示模拟一下.
和之前一样, 当更改其中一个 Room 的背景颜色后, 其余 Room 也会被重新渲染. 原因为,
flipLight
会导致RoomProvider
重新渲染, 导致每次产生一份新的value
, value 引用变化导致所有 Room 作为 consumer 都被重新渲染了如果按照之前的做法, 尝试用
React.memo
,useMemo
和useCallback
进行性能优化, 代码大致如下:仍旧失败, 原因在于, 虽然使用
React.memo
包裹了 Room 组件, 但由于内部又使用了useContext
, 同时lights
状态其实每次都是变化的, 因此即使使用了 useMemo, 每次 value 还是不一样, 这样 Room 组件作为消费者又被迫重新被渲染.要解决这个问题需要将 useContext 抽出来, 也就是不能再
React.memo
里使用, 因为React.memo
优化只针对 props. 同时因为 lights 一直变化的缘故, 传递的状态最好和之前一样是isLit
这种单一的状态, 而非整个完整的状态. 修改后的代码如下:这里新抽出一个高阶函数
withRoom
, 在这个高阶组件里进行 context 的消费, 然后返回需要的组件, 传递的 props 均为单一的属性, 而非一直会变化的 lights 状态, 这样React.memo
就能进行优化了.可以看到这样的优化代码非常丑陋, 而且可能会需要花费一些时间.
Redux
如果使用 Redux 来编写一般就不需要考虑这些性能优化的问题, 因为 Redux 内部其实都有做好这些脏活. 这里使用 Redux Toolkit 实现:
这里使用了
useSelector
进行状态的获取,useSelector
默认行为是在一个 action 被 dispatch 之后, 会对返回的选取状态进行严格比较, 如果相同组件不渲染, 否则重新渲染.isLit
作为 primitive type, 能够进行严格地址比较, 因此不再触发重新渲染.当然也可以使用
connect
和mapState
, 而且性能方面会比useSelector
更好, 因为做的是浅比较, 且 connect 返回的组件是用 React.memo 包裹的. 这里不多细究, 细节方面可以查阅官方文档总结
这篇文章的示例参考的是一个视频: React Context API vs. Redux 有兴趣可以观看视频, 印象可能更深
参考
The text was updated successfully, but these errors were encountered: