最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 在React 18中自动批处理以减少渲染次数(译文)

    正文概述 掘金(小许_)   2021-08-21   640

    原文地址:在React 18中自动批处理以减少渲染次数

    概述

    React 18增加了开箱即用的性能改进,默认做了更多的批处理,不再需要在应用程序或库代码中手动批处理更新。这篇文章将解释什么是批处理,它以前是如何工作的,以及有什么变化。

    什么是批处理?

    分批是指React将多个状态更新分组到一个重新渲染中以获得更好的性能。

    例如,如果你在同一个点击事件里有两个状态更新,React总是把它们分到一个重新渲染中。如果你运行下面的代码,你会看到每次点击时,React只执行一次渲染,尽管你设置了两次状态。

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        setCount(c => c + 1); // 还没有重新渲染
        setFlag(f => !f); // 还没有重新渲染
        // React 只会在最后重新渲染一次(这是批处理!)
      }
    
      return (
        <div>
          <button onClick={handleClick}>Next</button>
          <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
        </div>
      );
    }
    
    • ✅ Demo: React 17 事件处理程序中的批处理. (注意在控制台中每次点击都有一个渲染)

    这对性能非常有用,因为它避免了不必要的重新渲染。它还可以防止您的组件呈现仅更新一个状态变量的“半完成”状态,这可能会导致错误。这可能会提醒您,当您选择第一道菜时,餐厅服务员不会跑到厨房,而是等您完成订单。

    然而,React 关于何时批量更新并不一致。例如,如果你需要获取数据,然后在上面的 handleClick 中更新 state,那么 React 不会批量更新,而是执行两次独立的更新。

    这是因为React过去只在浏览器事件(如点击)期间进行批量更新,但在这里,我们是在事件已经被处理后(在fetch callback)才更新状态。

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        fetchSomething().then(() => {
          // React 17 and earlier does NOT batch these because
          // they run *after* the event in a callback, not *during* it
          setCount(c => c + 1); // Causes a re-render
          setFlag(f => !f); // Causes a re-render
        });
      }
    
      return (
        <div>
          <button onClick={handleClick}>Next</button>
          <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
        </div>
      );
    }
    
    • ? Demo: React 17 不批处理外部事件处理程序. (注意控制台中每次点击两次渲染)

    在 React 18 之前,我们只在 React 事件处理程序期间批量更新。默认情况下,React 中不会对 promisesetTimeout、原生事件处理程序或任何其他事件中的更新进行批处理。

    什么是自动批处理?

    从带有 createRoot 的 React 18 开始,所有更新都将自动批处理,无论它们来自何处。

     这意味着timeoutspromisesnative events处理程序或任何其他事件内的更新将以与 React 事件内的更新相同的方式进行批处理。我们希望这会导致更少的渲染工作,从而在您的应用程序中获得更好的性能:

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        fetchSomething().then(() => {
          // React 18和更高版本会批量处理这些内容
          setCount(c => c + 1);
          setFlag(f => !f);
          // React 只会在最后重新渲染一次(这是批处理!)
        });
      }
    
      return (
        <div>
          <button onClick={handleClick}>Next</button>
          <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
        </div>
      );
    }
    
    • ✅ Demo: 即使在事件处理程序之外,也可以使用 createRoot 批处理 React 18! (注意控制台中的每次点击渲染!)
    • ? Demo: 带有旧版渲染的 React 18 保留了旧行为 (请注意控制台中每次单击两次渲染)

    无论更新发生在何处,React 都会自动批量更新,因此:

    function handleClick() {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React 只会在最后重新渲染一次(这是批处理!)
    }
    

    行为与此相同:

    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React 只会在最后重新渲染一次(这是批处理!)
    }, 1000);
    

    行为与此相同:

    fetch(/*...*/).then(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React 只会在最后重新渲染一次(这是批处理!)
    })
    

    行为与此相同:

    elm.addEventListener('click', () => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React 只会在最后重新渲染一次(这是批处理!)
    });
    

    如果我不想批处理怎么办?

    通常,批处理是安全的,但某些代码可能依赖于在状态更改后立即从 DOM 中读取某些内容。对于这些用例,您可以使用 ReactDOM.flushSync() 选择退出批处理:

    import { flushSync } from 'react-dom'; // Note: react-dom, not react
    
    function handleClick() {
      flushSync(() => {
        setCounter(c => c + 1);
      });
      // React 现在已经更新了 DOM
      flushSync(() => {
        setFlag(f => !f);
      });
      // React 现在已经更新了 DOM
    }
    

    我们不期望这是个普遍现象

    这对 Hooks 有什么影响吗?

    如果您使用 Hooks,我们希望自动批处理在绝大多数情况下都能“正常工作”。 (如果没有,请告诉我们!)

    这对 Classes 有什么影响吗?

    请记住,更新 期间 React 事件处理程序一直是批处理的,因此对于这些更新没有任何更改。

     在类组件中存在边缘情况,这可能是一个问题。 

    类组件有一个实现的怪癖,它可以同步读取事件内部的状态更新。这意味着您将能够在调用 setState 之间读取 this.state

    handleClick = () => {
      setTimeout(() => {
        this.setState(({ count }) => ({ count: count + 1 }));
    
        // { count: 1, flag: false }
        console.log(this.state);
    
        this.setState(({ flag }) => ({ flag: !flag }));
      });
    };
    

    在React 18中,情况不再是这样了。因为即使在setTimeout中的所有更新都是分批进行的,React不会同步渲染第一个setState的结果--渲染发生在浏览器的下一个时间段。所以渲染还没有发生。

    handleClick = () => {
      setTimeout(() => {
        this.setState(({ count }) => ({ count: count + 1 }));
    
        // { count: 0, flag: false }
        console.log(this.state);
    
        this.setState(({ flag }) => ({ flag: !flag }));
      });
    };
    

    见 sandbox.

    如果这是升级到 React 18 的障碍,您可以使用 ReactDOM.flushSync 强制更新,但我们建议谨慎使用:

    handleClick = () => {
      setTimeout(() => {
        ReactDOM.flushSync(() => {
          this.setState(({ count }) => ({ count: count + 1 }));
        });
    
        // { count: 1, flag: false }
        console.log(this.state);
    
        this.setState(({ flag }) => ({ flag: !flag }));
      });
    };
    

    见 sandbox.

    这个问题并不影响带有Hooks的函数组件,因为设置状态并不更新来自useState的现有变量。

    function handleClick() {
      setTimeout(() => {
        console.log(count); // 0
        setCount(c => c + 1);
        setCount(c => c + 1);
        setCount(c => c + 1);
        console.log(count); // 0
      }, 1000)
    

    虽然当您采用 Hooks 时这种行为可能令人惊讶,但它为自动批处理铺平了道路。

    unstable_batchedUpdates 怎么样?

    一些React库使用这个未记录的API来强制事件处理程序之外的setState被分批处理。

    import { unstable_batchedUpdates } from 'react-dom';
    
    unstable_batchedUpdates(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
    });
    

    这个 API 在 18 中仍然存在,但不再需要它了,因为批处理是自动发生的。我们不会在 18 中删除它,尽管在流行的库不再依赖于它的存在之后,它可能会在未来的主要版本中被删除。

    在React 18中自动批处理以减少渲染次数(译文)


    起源地下载网 » 在React 18中自动批处理以减少渲染次数(译文)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元