前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React-利用React-Profiler提升应用性能

React-利用React-Profiler提升应用性能

作者头像
前端柒八九
发布2022-08-25 15:32:00
2K0
发布2022-08-25 15:32:00
举报
文章被收录于专栏:柒八九技术收纳盒

大家好,我是柒八九。

在前面的-「性能优化」系列中,我们通过网络和页面渲染的角度来阐述,如何针对一个页面进行优化提效。

上面的一些优化方式,无论使用何种前端框架(React/Vue)都适用,而今天,我们来讲讲如何使用React Profiler针对React项目进行性能分析和渲染提效。

老样子,话不多说,开始步入正题。

你能所学到的知识点

  1. React Profiler 的组成 「推荐阅读指数」 ⭐️⭐️⭐️
  2. 如何通过React Profiler查询并改正页面耗时操作 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️

你还在为得到一个组件的渲染次数渲染时间而发愁吗?

你还在使用console.log来计算这些重要的性能指标吗?

你还在为React性能优化而抓狂吗?

不要998,只要..... (走错片场了)重新来

解决以上令你“魂牵梦绕”的问题,React-Profiler你值得拥有。它足够老牌(2018年推出),它背景足够硬(有官方撑腰)

所以,总之就是要想React应用,变得丝滑,用它就对了。

案例实现

为了展示React Profiler,我们将有一个非常简单的应用程序。

  • 有一个自动生成的数字列表
  • 可以通过在文本框中输入的搜索词进行过滤

页面的整体结构 Filter/List

代码语言:javascript
复制
import { Chance } from 'chance';
const chance = new Chance();
// 生成一个长度为200,内容整数的随机数组
const items = Array.from(
  { length: 200 }, 
  () => `${chance.integer()}`
);

export const FilterableList = () => {
    const [searchTerm, setSearchTerm] = useState('');
    return <div className={'filterableList'}>
        <Filter onValueUpdated={setSearchTerm} />
        <List entries={items.filter(
                item => item.includes(searchTerm)
              )} 
        />
    </div>
}

组件List/ListItem的实现

代码语言:javascript
复制
export interface ListProps {
  entries: string[];
}

export const List: FC<ListProps> = ({entries}) => {
  return (
    <div className="list">
      {entries.map((value, index) => 
        <ListItem key={index} value={value}/>
      )}
    </div>
  );
}

interface ListItemProps {
    value: string;
}


export const ListItem: FC<ListItemProps> = 
({value}) => <div className={'item'}>{value}</div>

就是一个常规不能再常规的问题。一个长List,用于展示数据信息,一个输入框,用于检索列表信息。

React Profiler

我们假设,在你的浏览器环境下,已经安装了React-Dev-Tools的插件。如果没有,需要做一些额外的处理工作。如果能访问到「谷歌商店」,那就进行按照处理。如果不行的话,搜索react-devtools-extensions,然后按照指定的步骤进行操作。

「一旦安装,React-Dev-Tools能够被任何使用React技术栈构建的网站所访问」

React应用标签下,打开控制台,就会看到指定的插件信息。

针对页面的分析,我们需要先利用Profiler的录制功能,进行页面渲染过程的录制,然后才能对该渲染过程进行分析。

但是在开始录制之前,我们需要在Profiler启用一个重要的设置。点击右上角的齿轮图标。

ProfilerTab下,勾选第一个选项--记录每个组件渲染的原因

第二个选项(隐藏下面的提交)也很有用,特别是当你有很多commit,想过滤掉不重要的提交(那些低于某个阈值的commit)。

开始剖析

点击「蓝色」按钮,开始一个剖析工作。

或者,点击「循环按钮」使得「重新加载页面」并立即开始信息收录工作。

收录开始后,进行一些页面操作,然后点击「红色」按钮停止信息收录

对于测试案例,在文本框中输入111,然后一个一个地删除数字(111->11->1->'')。

停止收录后,得到的结果如下。


Profiler UI 界面

Profiler的UI界面在逻辑上可分为4个主要部分。

  1. 「图表类型」
    • 火焰图
    • 排序图
  2. 「图表区域」--在应用程序的剖析切片中,代表某次commit对应的组件渲染时间的相关信息。
  3. 「提交区域」--每个条形图代表应用程序在整个录制阶段所有的commit操作。每当你通过点击选择一个commit「图表区域」「提交信息」就会相应地更新。
  4. 「提交信息面板」--关于单个选定的commit阶段或单个选定组件的细节。

提交区域

React调和算法分为两个阶段:「渲染」「提交」

  • 「渲染阶段」收录组件进行何种的信息变更。在这个阶段,React 调用 render,然后将结果与之前的render进行比较( diff 算法)。
  • 「提交阶段」React将需要变更的一些列操作,更新到真正的DOM树上。

具体的实现细节,可以参考React-Fiber机制1/React-Fiber机制2

下面展示了,针对类组件和函数组件的渲染步骤。

「类组件的生命周期」

「函数组件的渲染步骤」

如前所述,「提交区域的每个条形图代表一个commit,条形图越高,提交的时间越长」。这些提交也可以通过一个从绿色到黄色的颜色梯度来区分

  • 黄色是性能较差的commit
  • 绿色是性能较好的commit

因此,「较高的黄条代表commit时间比较短的绿条长」

图表 - 火焰图

火焰图表示应用程序在「特定commit中的渲染树」。图表中的每一条都代表一个React组件。这些组件从上到下依次为根组件和叶子节点(根部是最上面的组件,叶子是最下面的)。

正如你所看到的,HeaderFilterableListApp的孩子,所以它们并排在第二行,而第一行是App

❝条形图的「宽度」表示该「组件及其子组件的渲染时间」 条形图的颜色代表组件「本身渲染的时间」(绿色代表快,黄色代表慢) ❞

因此,在上面的例子中,FilterableList 的宽度代表 FilterableList及其孩子节点List的渲染时间。

另一方面,你可以看到FilterableList是绿色的,List是黄色的,这与数字相关--FilterableList只花了0.5ms渲染,List花了1.6ms渲染。

但如果在某次提交中,某个组件根本没有被渲染,会发生什么情况呢?

我们选择第四次commit的情况来分析。

AppHeader组件在过滤时不会改变,所以它们只在第一次commit时被渲染一次。在接下来的commit中,这两个组件都是「灰色」的,不过,它们看起来还是有点不同。

  • 「灰色填充」--在这次提交中没有渲染的组件,但它是「渲染路径的一部分」(例如,App没有渲染,但它是FilterableList的父组件,而FilterableList被渲染)。
  • 「灰色渐变条纹」--在本次commit中没有渲染的组件,也不是渲染路径的一部分(例如,Header没有渲染,但它也没有任何子代被渲染)。

同时,尽管App组件没有渲染,但它仍然有一个宽度。

所以,让我们把这个定义细化一下。

「条形图」

  • 「宽度」代表该组件最后一次被渲染时花费的时间
  • 「颜色」代表作为当前commit的一部分花费的时间

「last but not least」,你可以通过点击某个组件来「放大」「缩小」图表。

「缩小组件」 -- 从App整个commitFilter组件

「放大组件」-- 重新点击上层组件

图表 - 排序图

与火焰图类似,排序图表示一个单一的提交。然而,与火焰图不同的是,组件是「按渲染时间而不是按渲染顺序排列的」

这意味着,「渲染时间最长的组件在最上面」

另一个区别是,「组件的条形宽度代表了该组件的渲染时间」,不包括其子组件。这意味着「颜色和宽度之间有直接的关联」

正如你所看到的,List花了最长的时间来渲染,所以它位于顶部,它在条形图中是最宽的,它在条形图中是最黄的。

「在这次commit过程中没有渲染的组件不会出现在排序图中」

与火焰图类似,通过点击组件可以放大和缩小。

提交信息面板

「提交信息面板」有两种不同的用途。

  1. 展示整个应用的渲染信息

当没有选择任何组件时(放大),它会显示当前在commit过程中的commit概况。数据包括commit的时间(自应用程序启动以来),渲染的时间,以及优先级。

  1. 展示单个组件的渲染信息

当你在某个图表区域中点击一个组件(放大它)时,「提交信息面板」会显示这个组件的细节。这包括该组件在这个特定的commit过程中「渲染的原因」(如果你在设置中启用了这个选项,我们在刚开始的时候,有过介绍)以及带有时间戳的「提交列表」。这个列表是交互式的,允许你在这个特定组件参与的不同提交之间轻松浏览。

案例分析

现在我们已经熟悉了React Profiler,让我们看看如何将这些知识应用到实际开发中。

我们继续采用,文章开头的示例代码。

组件内部的逻辑是非常直接的,所以很难改进。

相反,我们将专注于渲染性能,尝试「减少渲染次数」。由于我们在commit之间所做的只是过滤,我们会假设item被渲染一次,然后在过滤操作后从DOM中移除。这意味着ListItem不应该在过滤时被渲染两次。然后,在我们提供的实验案例中,ListItem在每次commit的时候,都会被渲染。

让我们放大第二个commit中的一个ListItem,试着弄清楚。

放大后为我们提供了有用的信息--该item被重新渲染,因为它的propsvalue属性发生变化了。

为什么值会改变?因为,每次我们过滤列表时都会创建一个新的数组。由于我们使用item-index作为ListItem组件的键,每次我们改变过滤值时,对应的数据信息也会不同。

例如,在第一次渲染时,数组中的第一个item是用一个key=1的组件渲染的。然而,在第二次渲染时,当我们从数组中过滤掉一些值时,第一个item可能是不同的。React 会重新使用第一次渲染时的key=1的组件,但由于第一个item本身发生了变化,其内部包含的信息也发生了变化,因此要重新渲染。

为了解决这个问题,我们将在第一次创建数组时为数组中的每个item分配一个ID,并将其作为组件的键,而不是使用项目索引。

页面的整体结构 Filter/List

代码语言:javascript
复制
import { Chance } from 'chance';
const chance = new Chance();
// 生成一个长度为200,内容整数的随机数组
const items = Array.from(
  { length: 200 }, 
  (_, index) => ({ value: `${chance.integer()}`, id: index})
);

export const FilterableList = () => {
    const [searchTerm, setSearchTerm] = useState('');
    return <div className={'filterableList'}>
        <Filter onValueUpdated={setSearchTerm} />
        <List entries={items.filter(
                item => item.includes(searchTerm)
              )} 
        />
    </div>
}

组件List/ListItem的实现

代码语言:javascript
复制
export interface ListProps {
  entries: {value: string, id: number}[];
}

export const List: FC<ListProps> = ({entries}) => {
  return (
    <div className="list">
      {entries.map(({id, value}) => 
        <ListItem key={id} value={value}/>
      )}
    </div>
  );
}

interface ListItemProps {
    value: string;
}


export const ListItem: FC<ListItemProps> = 
({value}) => <div className={'item'}>{value}</div>

经过所谓的优化处理,在每次commit发生时,ListItem仍然会被重新渲染。

通过,查看「提交信息面板」中的渲染原因,发现是由于ListItems的父组件发生了渲染,导致了它也被重新渲染。而父组件重新渲染,是不管子组件内部的值是否发生变化。是一种强制性的渲染机制。

显然,这是一种不理想的渲染方式,而React也提供了一种规避这种无效渲染的方式-- React.memo

代码语言:javascript
复制
export const ListItem: FC<ListItemProps> = 
React.memo(({value}) => <div className={'item'}>{value}</div>)

经过React.memo处理后,在进行过滤操作,ListItems不会发生重新渲染了。

通过一个简单的例子展示了React-Profiler的配置和使用方式,让一些不易察觉的问题直观的显现出来,并通过针对某个组件进行放大处理,找到其渲染过长的原因,对其对症下药。然后,做到药到病除。

愿我们的应用,不在卡顿。

后记

「分享是一种态度」

参考资料:

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

本文分享自 前端柒八九 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你能所学到的知识点
  • 案例实现
  • React Profiler
    • 开始剖析
    • Profiler UI 界面
      • 提交区域
        • 图表 - 火焰图
          • 图表 - 排序图
            • 提交信息面板
            • 案例分析
            • 后记
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档