前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 17 对 usEffect 的优化,提升 commit 阶段 10% 的性能

React 17 对 usEffect 的优化,提升 commit 阶段 10% 的性能

作者头像
ConardLi
发布2021-07-16 11:24:34
8240
发布2021-07-16 11:24:34
举报
文章被收录于专栏:code秘密花园

前几天刚体验过 React 18 ,感觉非常 Nice,没有看到的小伙伴不要错过:

【第一批吃螃蟹】试用 React 18 !

是不是惊叹于 React 团队的更新速度?React 17 没什么存在感, React 18 就来了?实际上 React 17 本身就是一个过渡版本,它的主要目的是帮助我们进行渐进式升级。

但是没有啥存在感的 React 17 也做了很多非常棒的优化,比如我们今天聊的 useEffect 清理机制的变更。

当组件卸载时,React 会执行清理。比如,如果你在 useEffect 方法中返回一个函数,它就会在组件卸载时执行。

代码语言:javascript
复制
useEffect(() => {
  // This is the effect itself.
  return () => {
    // This is its cleanup.
  };
});

React 17 之前,useEffect 的清理函数会在 commit 阶段执行 。这意味着当组件卸载时,React 先会执行清理函数,然后才会更新屏幕。它类似于 componentWillUnmount 这个生命周期的行为。

commit 阶段是什么不记得了?我们先来回顾一下

React 渲染的两个阶段

React Fiber 引入了异步渲染,有了异步渲染之后,React 组件的渲染过程是分时间片的,不是一口气从头到尾把子组件全部渲染完,而是每个时间片渲染一点,然后每个时间片的间隔都可去看看有没有更紧急的任务(比如用户按键),如果有,就去处理紧急任务,如果没有那就继续照常渲染。

根据 React Fiber 的设计,一个组件的渲染被分为两个阶段:

  • 第一个阶段(也叫做 render 阶段)是可以被 React 打断的,一旦被打断,这阶段所做的所有事情都被废弃,当 React 处理完紧急的事情回来,依然会重新渲染这个组件,这时候第一阶段的工作会重做一遍;
  • 第二个阶段叫做 commit 阶段,一旦开始就不能中断,也就是说第二个阶段的工作会稳稳当当地做到这个组件的渲染结束。

两个阶段的分界点,就是 render 函数。render 函数之前的所有生命周期函数(包括 render)都属于第一阶段,之后的都属于第二阶段。

执行延迟

回到刚刚的问题,每次组件卸载都需要先运行一次清理函数才更新屏幕,这对于较大的应用程序,是会有一些性能影响的。比如在切换标签页的时候,可能会感到卡顿。

在 React 17 之后,useEffect 的清理函数会延迟到 commit 阶段完成之后才会执行。换句话说, useEffect 清理函数被更改为异步执行,比如组卸载时,清理函数会在屏幕更新后执行。

Profiler API

我们可以使用 Profiler API 来测试一下这个改变会不会提升我们的组件性能。

Profiler API 可以测试 React 组件的渲染性能,如果我们要测试一个组件,可以把它放到 Profiler 组件中,组件接收一个 onRender 函数,当组件每次 commit 更新时,函数都会执行:

代码语言:javascript
复制
render(
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Main {...props} />
  </App>
);

onRender 中的下面两个参数我们可能会用到:

  • phase: "mount" | "update" :确定组件是第一次挂载还是更新
  • commitTime:组件 commit 更新时的时间戳

一个例子 ?

下面我们来看一个简单的例子,当我们点击 Show users 按钮时,它会通过 API 获取用户列表并渲染用户列表。如果点击 Hide usersUserInfo 这个组件会被卸载。

代码语言:javascript
复制
//App.jsx

export default function App() {

  const callback = (phase, actualTime, baseTime, commitTime) => {
    console.group(phase);
    console.table({
      commitTime,
    });
    console.groupEnd();
   }

  return (
    <Profiler id="users" onRender={callback}>
      <div className="App">
        <section>
          <h2>Users:</h2>
          <Button title="Users">
            <Users />
          </Button>
        </section>
      </div>
    </Profiler>
  );
}
代码语言:javascript
复制
//Button.jsx

export default function Button({ title, children }) {
  let [toggle, setToggle] = useState(false);
  return (
    <section>
      <button className="primary" onClick={() => setToggle(!toggle)}>
        {toggle ? `HIDE ${title}` : `SHOW ${title}`}
      </button>
      {toggle && children}
    </section>
  );
}
代码语言:javascript
复制
//Users.jsx

export default function UserInfo() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();
    const signal = abortController.signal;
    fetch(USERS_API, { signal: signal })
      .then(results => results.json())
      .then(data => {
        setUser(data);
      });
    return function cleanup() {
      console.log('I am in cleanup function');
      abortController.abort();
    };
  }, []);

  return (
    <div>
      <h4>Users</h4>
      {user === null ? (
        <p>Loading User Data ...</p>
      ) : (
        <pre>{JSON.stringify(user, null, 4)}</pre>
      )}
    </div>
  );
}

React 17 之前,先回执行清理函数,然后屏幕才会被更新,这会增加 commit 时间。

React 17 之后,清理函数会在在屏幕更新后异步执行,这会减少 commit 时间。

嗯,就是这样一个小的优化,提升了组件卸载时 10% 的渲染性能,不要小看它,正是这些大大小小的优化让 React 应用程序的体验变得越来越好。

不得不佩服 React 团队,真的是致力于极致的用户体验!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • React 渲染的两个阶段
  • 执行延迟
  • Profiler API
  • 一个例子 ?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档