翼度科技»论坛 编程开发 JavaScript 查看内容

图文示例讲解useState与useReducer性能区别

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
引言

稍微深入了解过
  1. useState
复制代码
的同学都知道 ——
  1. useState
复制代码
其实是预置了
  1. reducer
复制代码
  1. useReducer
复制代码
。具体来讲,他预置的
  1. reducer
复制代码
实现如下:
  1. function basicStateReducer(state, action) {
  2.   // $FlowFixMe: Flow doesn't like mixed types
  3.   return typeof action === 'function' ? action(state) : action;
  4. }
复制代码
那按理来说,
  1. useState
复制代码
  1. useReducer
复制代码
性能应该完全一致才对。但实际上,他们的性能并不一样。本文就来聊聊他们的细微差别。

一个严重的bug

  1. v18
复制代码
之前,特定场景下,
  1. useReducer
复制代码
存在一个严重的
  1. bug
复制代码
。假设我们要挂载如下
  1. App
复制代码
组件:
  1. function App() {
  2.   const [disabled, setDisabled] = React.useState(false);
  3.   return (
  4.     <>
  5.       <button onClick={() => setDisabled((prev) => !prev)}>Disable</button>
  6.       <div>{`Disabled? ${disabled}`}</div>
  7.       <CounterReducer disabled={disabled} />
  8.     </>
  9.   );
  10. }
复制代码
通过点击按钮,可以切换
  1. disabled
复制代码
状态,并将
  1. disabled
复制代码
作为
  1. props
复制代码
传递给
  1. CounterReducer
复制代码
组件。
  1. CounterReducer
复制代码
组件的实现如下:
  1. function CounterReducer({ disabled }) {
  2.   const [count, dispatch] = useReducer((state) => {
  3.     if (disabled) {
  4.       return state;
  5.     }
  6.     return state + 1;
  7.   }, 0);
  8.   return (
  9.     <>
  10.       <button onClick={dispatch}>reducer + 1</button>
  11.       <div>{`Count ${count}`}</div>
  12.     </>
  13.   );
  14. }
复制代码
  1. count
复制代码
状态初始为0,当
  1. disabled props
复制代码
  1. true
复制代码
时,点击reducer + 1按钮
  1. count
复制代码
不会变化。

  1. disabled props
复制代码
  1. false
复制代码
时,点击reducer + 1按钮
  1. count
复制代码
会加1。

现在问题来了,当
  1. disabled props
复制代码
  1. true
复制代码
时(此时
  1. count
复制代码
为0),我们点击reducer + 1按钮5次,然后再点击Disable按钮
  1. disabled props
复制代码
会变为
  1. false
复制代码
),此时
  1. count
复制代码
为多少呢?
按照代码逻辑,改变
  1. disabled
复制代码
  1. count
复制代码
不会造成影响,所以他应该保持原始状态不变(即为0)。

但在
  1. v18
复制代码
之前,他会变成5。

但是,如果我们用
  1. useState
复制代码
实现同样逻辑的
  1. useReducer
复制代码
  1. function CounterState({ disabled }) {
  2.   const [count, dispatch] = useState(0);
  3.   function dispatchAction() {
  4.     dispatch((state) => {
  5.       if (disabled) {
  6.         return state;
  7.       }
  8.       return state + 1;
  9.     });
  10.   }
  11.   return (
  12.     <>
  13.       <button onClick={dispatchAction}>state + 1</button>
  14.       <div>{`Count ${count}`}</div>
  15.     </>
  16.   );
  17. }
复制代码
就能取得符合预期的效果。
所以说,
  1. useReducer
复制代码
的实现在特殊场景下是有
  1. bug
复制代码
的(v18之前)。

bug是如何产生的

产生这个
  1. bug
复制代码
的原因在于
  1. React
复制代码
内部的一种被称为
  1. eager state
复制代码
的性能优化策略。
简单的说,对于类似如下这样的,即使多次触发更新,但状态的最终结果不变的情况(在如下例子中
  1. count
复制代码
始终为0):
  1. function App() {
  2.   const [count, dispatch] = useState(0);
  3.   return <button onClick={() => dispatch(0)}>点击</button>;
  4. }
复制代码
  1. App
复制代码
组件是没有必要
  1. render
复制代码
的。这就省去了
  1. render
复制代码
的性能开销。
要命中
  1. eager state
复制代码
,有个严格的前提 —— 状态更新前后不变。
我们知道,
  1. React
复制代码
中有两种更新状态的方式:

  • 传递新的状态
  1. // 定义状态
  2. const [count, dispatch] = useState(0);
  3. // 更新状态
  4. dispatch(100)
复制代码

  • 传递更新状态的函数
  1. // 定义状态
  2. const [count, dispatch] = useState(0);
  3. // 更新状态
  4. dispatch(oldState => oldState + 100)
复制代码
那么,对于方式1,要保证状态不变很简单,只需要全等比较变化前后的状态,如果他们一致就能进入
  1. eager state
复制代码
策略。
对于方式2,就略微复杂点,需要同时满足2个条件:

  • 状态更新函数本身不变
  • 通过状态更新函数计算出的新状态也不变
比如,下述代码就同时满足2个条件,但如果将
  1. change
复制代码
放到
  1. App
复制代码
内就不满足条件1(
  1. App
复制代码
组件每次
  1. render
复制代码
时都会创建新的
  1. change
复制代码
函数):
  1. // 状态更新函数本身不变
  2. function change(oldState) {
  3.   // 新状态也不变
  4.   return oldState;
  5. }
  6. function App() {
  7.   const [count, dispatch] = useState(0);
  8.   // 状态更新函数每次render都会变化
  9.   // function change(oldState) {
  10.      // 新状态不变
  11.      // return oldState;
  12.   // }
  13.   return <button onClick={() => dispatch(change)}>点击</button>;
  14. }
复制代码
类似的情况,在
  1. useState
复制代码
的实现中,虽然他是预置了
  1. reducer
复制代码
  1. useReducer
复制代码
,但他预置的
  1. reducer
复制代码
的引用是不变的,所以用他实现的文章开篇的例子可以命中优化策略。
  1. useReducer
复制代码
在特定场景下的
  1. bug
复制代码
就与此相关。并不是说
  1. bug
复制代码
产生的原因是
  1. useReducer
复制代码
一定没命中优化策略,而是说相比于
  1. useState
复制代码
,他命中优化策略很不稳定。

v18之后的改变

既然
  1. bug
复制代码
来源于不稳定的性能优化策略,在没有完美的解决方案之前,
  1. React
复制代码
是如何在
  1. v18
复制代码
中修复这个
  1. bug
复制代码
的呢?
答案是 —— 移除
  1. useReducer
复制代码
  1. eager state
复制代码
策略。也就是说,在任何情况下,
  1. useReducer
复制代码
都不再有
  1. useState
复制代码
存在的这个性能优化策略了。
这就导致在特定场景下,
  1. useReducer
复制代码
的性能弱于
  1. useState
复制代码

比如在v18在线示例中,同样的逻辑用
  1. useState
复制代码
实现,不会有冗余的
  1. render
复制代码
,而
  1. useReducer
复制代码
会有。

总结

在考虑性能优化时,如果
  1. useState
复制代码
  1. useReducer
复制代码
都能满足需要,或许
  1. useState
复制代码
是更好的选择。
以上就是useState与useReducer性能区别图文示例详解的详细内容,更多关于useState useReducer性能区别的资料请关注脚本之家其它相关文章!

来源:https://www.jb51.net/javascript/2844414oy.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具