最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react18 批处理解决不必要的render(译文)

    正文概述 掘金(bug设计师)   2021-07-29   644

    [原文地址](Automatic batching for fewer renders in React 18 · Discussion #21 · reactwg/react-18 · GitHub)

    写在前面

    最近在学习React18中相关更新,也可以说是心血来潮想翻译一些东西,也是为了自己学习,也想跟社区同学一起学习,初次翻译,有很多语义可能不同,同学们如果遇到不对的地方,可以查阅原文,也期望同学能指出文中不合事实的地方。

    概述

    react 18添加开箱即用的性能改进,通过默认情况下进行更多的批处理,删除在应用程序或库代码中手动的批量更新。 这篇文章将解释批处理是什么,它以前的工作原理,以及改变了什么。

    什么是批处理

    批处理是指为了更好的性能将一组状态更新,融合进一次单独的重渲染中

    例如,你如果有两个状态需要在一次点击事件中更新,React总会把它们放入一次重渲染中。如果你点击以下代码,你会发现每次你点击的时候,React只会重渲染一次,尽管你更新了两次状态。

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        setCount(c => c + 1); // 暂时不回重渲染
        setFlag(f => !f); // 暂时不回重渲染
        // React 只会最后重渲染一次
        // React will only re-render once at the end (that's batching!)
      }
    
      return (
        <div>
          <button onClick={handleClick}>Next</button>
          <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
        </div>
      );
    }
    
    

    Demo: React 17 batches inside event handlers(注意console 中的信息)

    这对性能是非常好的提升,因为它避免了很多不必要的重渲染。他还能阻止你的组件因为单个状态的更新而显示出一个半成品的状态,有些甚至会造成一些问题。这可能会提醒你,直到你点完单后,服务员才会去厨房下单,并不会在你点第一道菜时就去。

    但是在批处理中,react的表现并不一致。例如,如果你获取线上数据,然后在回调中更新状态,react并不会对这些更新进行批处理,而是进行独立的更新。

    这是因为react过去只在浏览器时间中进行批处理(比如click),但是我们却在处理事件的回调中更新状态

    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 does NOT batch outside event handlers. (注意在一次点击中两次更新)

    直到React18,我们只在React事件处理中进行批处理。我们不会在promise setTimeout native Event Handle中进行批处理。

    什么是自动批处理

    让我们关注到react18,所有的更新都会被自动批处理,不管它来自哪里。

    这意味着,promise native Event Handle 或者任何其他的事件,都会像React事件样被批处理。我们预计这会导致更少的重渲染,并在你的应用中有更好的性能。

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        fetchSomething().then(() => {
          // React 18 and later DOES batch these:
          setCount(c => c + 1);
          setFlag(f => !f);
          // React will only re-render once at the end (that's batching!)
        });
      }
    
      return (
        <div>
          <button onClick={handleClick}>Next</button>
          <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
        </div>
      );
    }
    
    
    • ✅ Demo: React 18 with createRoot batches even outside event handlers! (Notice one render per click in the console!)

    • ? Demo: React 18 with legacy render keeps the old behavior (Notice two renders per click in the console.)

    React 会自动批处理,不管更新来自哪里,如下例

    function handleClick() {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React will only re-render once at the end (that's batching!)
    }
    

    如下例子表现相同

    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React will only re-render once at the end (that's batching!)
    }, 1000);
    
    fetch(/*...*/).then(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React will only re-render once at the end (that's batching!)
    })
    
    elm.addEventListener('click', () => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // React will only re-render once at the end (that's batching!)
    });
    

    如果我不想批处理怎么办

    通常,批处理是安全的,但是有些代码可能依赖状态更新后立马从DOM获取某些东西。对于以下情况,可以用ReactDOM.flushSync(),去跳出批处理

    import { flushSync } from 'react-dom'; // Note: react-dom, not react
    
    function handleClick() {
      flushSync(() => {
        setCounter(c => c + 1);
      });
      // React has updated the DOM by now
      flushSync(() => {
        setFlag(f => !f);
      });
      // React has updated the DOM by now
    }
    

    我们不希望这成为常用方法

    这是否会影响hook的功能

    如果你正在使用hook 我们期望批处理能正常工作(如果不能请告诉我们)

    这是否会影响class 的功能

    记住,在React时间处理中的更新总是会被批处理,所以对于这些更新是没有影响的。

    这里有一些边界case 能用来讨论一下

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

    在React18 中这不会是问题了,因为即使是在setTimeout 中的更新也会被批处理,React不会同步的渲染第一个setState,这个渲染会出现在浏览器的下一帧中,所以这个渲染还没有更新

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

    案例.

    如果这会成为你更新React18的阻碍,你可以使用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 }));
      });
    };
    

    案例.

    这个问题不回影响具有hook 的函数组件,因为更新状态不会更新useState中现存的变量(猜测是指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)
    

    虽然当你使用hook时,这种行为会惊讶到你,但他为自动批处理铺平了道路。

    关于unstable_batchedUpdates

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

    这个API在React18中仍然存在,但是不再需要他了,因为有自动批处理,我们暂时不回在18版本中移除,但是可能在主流React库不再依赖它之后移除


    起源地下载网 » react18 批处理解决不必要的render(译文)

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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