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

 更新时间:2023年05月16日 08:52:54   作者:卡颂  
这篇文章主要为大家介绍了useState与useReducer性能区别图文示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

稍微深入了解过useState的同学都知道 —— useState其实是预置了reduceruseReducer。具体来讲,他预置的reducer实现如下:

function basicStateReducer(state, action) {
  // $FlowFixMe: Flow doesn't like mixed types
  return typeof action === 'function' ? action(state) : action;
}

那按理来说,useStateuseReducer性能应该完全一致才对。但实际上,他们的性能并不一样。本文就来聊聊他们的细微差别。

一个严重的bug

v18之前,特定场景下,useReducer存在一个严重的bug。假设我们要挂载如下App组件:

function App() {
  const [disabled, setDisabled] = React.useState(false);
  return (
    <>
      <button onClick={() => setDisabled((prev) => !prev)}>Disable</button>
      <div>{`Disabled? ${disabled}`}</div>
      <CounterReducer disabled={disabled} />
    </>
  );
}

通过点击按钮,可以切换disabled状态,并将disabled作为props传递给CounterReducer组件。

CounterReducer组件的实现如下:

function CounterReducer({ disabled }) {
  const [count, dispatch] = useReducer((state) => {
    if (disabled) {
      return state;
    }
    return state + 1;
  }, 0);
  return (
    <>
      <button onClick={dispatch}>reducer + 1</button>
      <div>{`Count ${count}`}</div>
    </>
  );
}

count状态初始为0,当disabled propstrue时,点击reducer + 1按钮count不会变化。

disabled propsfalse时,点击reducer + 1按钮count会加1。

现在问题来了,当disabled propstrue时(此时count为0),我们点击reducer + 1按钮5次,然后再点击Disable按钮disabled props会变为false),此时count为多少呢?

按照代码逻辑,改变disabledcount不会造成影响,所以他应该保持原始状态不变(即为0)。

但在v18之前,他会变成5。

但是,如果我们用useState实现同样逻辑的useReducer

function CounterState({ disabled }) {
  const [count, dispatch] = useState(0);
  function dispatchAction() {
    dispatch((state) => {
      if (disabled) {
        return state;
      }
      return state + 1;
    });
  }
  return (
    <>
      <button onClick={dispatchAction}>state + 1</button>
      <div>{`Count ${count}`}</div>
    </>
  );
}

就能取得符合预期的效果。

所以说,useReducer的实现在特殊场景下是有bug的(v18之前)。

bug是如何产生的

产生这个bug的原因在于React内部的一种被称为eager state的性能优化策略。

简单的说,对于类似如下这样的,即使多次触发更新,但状态的最终结果不变的情况(在如下例子中count始终为0):

function App() {
  const [count, dispatch] = useState(0);
  return <button onClick={() => dispatch(0)}>点击</button>;
}

App组件是没有必要render的。这就省去了render的性能开销。

要命中eager state,有个严格的前提 —— 状态更新前后不变。

我们知道,React中有两种更新状态的方式:

  • 传递新的状态
// 定义状态
const [count, dispatch] = useState(0);
// 更新状态
dispatch(100)
  • 传递更新状态的函数
// 定义状态
const [count, dispatch] = useState(0);
// 更新状态
dispatch(oldState => oldState + 100)

那么,对于方式1,要保证状态不变很简单,只需要全等比较变化前后的状态,如果他们一致就能进入eager state策略。

对于方式2,就略微复杂点,需要同时满足2个条件:

  • 状态更新函数本身不变
  • 通过状态更新函数计算出的新状态也不变

比如,下述代码就同时满足2个条件,但如果将change放到App内就不满足条件1(App组件每次render时都会创建新的change函数):

// 状态更新函数本身不变
function change(oldState) {
  // 新状态也不变
  return oldState;
}
function App() {
  const [count, dispatch] = useState(0);
  // 状态更新函数每次render都会变化
  // function change(oldState) {
     // 新状态不变
     // return oldState;
  // }
  return <button onClick={() => dispatch(change)}>点击</button>;
}

类似的情况,在useState的实现中,虽然他是预置了reduceruseReducer,但他预置的reducer的引用是不变的,所以用他实现的文章开篇的例子可以命中优化策略。

useReducer在特定场景下的bug就与此相关。并不是说bug产生的原因是useReducer一定没命中优化策略,而是说相比于useState,他命中优化策略很不稳定。

v18之后的改变

既然bug来源于不稳定的性能优化策略,在没有完美的解决方案之前,React是如何在v18中修复这个bug的呢?

答案是 —— 移除useReducereager state策略。也就是说,在任何情况下,useReducer都不再有useState存在的这个性能优化策略了。

这就导致在特定场景下,useReducer的性能弱于useState

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

总结

在考虑性能优化时,如果useStateuseReducer都能满足需要,或许useState是更好的选择。

以上就是useState与useReducer性能区别图文示例详解的详细内容,更多关于useState useReducer性能区别的资料请关注脚本之家其它相关文章!

相关文章

  • React使用PropTypes实现类型检查功能

    React使用PropTypes实现类型检查功能

    这篇文章主要介绍了React高级指引中使用PropTypes实现类型检查功能的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • React实现文件分片上传和下载的方法详解

    React实现文件分片上传和下载的方法详解

    在当今的前端开发中,处理文件流操作已经成为一个常见的需求,无论是上传、下载、读取、展示还是其他的文件处理操作,都需要高效且可靠地处理二进制数据,本文将深入探讨如何使用 React 实现文件分片上传和下载,并介绍相关的基本概念和技术,需要的朋友可以参考下
    2023-08-08
  • 在create-react-app中使用css modules的示例代码

    在create-react-app中使用css modules的示例代码

    这篇文章主要介绍了在create-react-app中使用css modules的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • React中实现组件通信的几种方式小结

    React中实现组件通信的几种方式小结

    在构建复杂的React应用时,组件之间的通信是至关重要的,从简单的父子组件通信到跨组件状态同步,不同组件之间的通信方式多种多样,下面我们认识react组件通信的几种方式,需要的朋友可以参考下
    2024-04-04
  • React项目使用ES6解决方案及JSX使用示例详解

    React项目使用ES6解决方案及JSX使用示例详解

    这篇文章主要为大家介绍了React项目使用ES6解决方案及JSX使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • React利用scheduler思想实现任务的打断与恢复

    React利用scheduler思想实现任务的打断与恢复

    这篇文章主要为大家详细介绍了React如何利用scheduler思想实现任务的打断与恢复,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下
    2024-03-03
  • 关于React Native 无法链接模拟器的问题

    关于React Native 无法链接模拟器的问题

    许多朋友遇到React Native 无法链接模拟器的问题,怎么解决呢,本文给大家分享完整简便解决方法及配置例题,对React Native 链接模拟器相关知识感兴趣的朋友一起看看吧
    2021-06-06
  • 关于React状态管理的三个规则总结

    关于React状态管理的三个规则总结

    随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态),这篇文章主要给大家介绍了关于React状态管理的三个规则,需要的朋友可以参考下
    2021-07-07
  • react中实现拖拽排序react-dnd功能

    react中实现拖拽排序react-dnd功能

    这篇文章主要介绍了react中实现拖拽排序react-dnd功能,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • 如何在React Native开发中防止滑动过程中的误触

    如何在React Native开发中防止滑动过程中的误触

    在使用React Native开发的时,当我们快速滑动应用的时候,可能会出现误触,导致我们会点击到页面中的某一些点击事件,误触导致页面元素响应从而进行其他操作,表现出非常不好的用户体验。
    2023-05-05

最新评论