React应用程序的分析和优化

像我这样为高性能网站而奋斗的人经常在此上花费很多时间。 因此,现在我将一劳永逸地解决其接口是用React编写的低速Web资源的问题。 也就是说,我建议阅读此书的每个人今天都应该停止使用React。



材料的作者,我们今天出版的翻译,当然是在开玩笑。 在这里,我们将讨论如何优化React应用程序的性能。 顺便说一下,在开始之前,让我们考虑一下为什么通常需要网站优化。 也许可以说,与优化之前相比,有更多的人可以使用该网站。

引言


如何优化网站? 您如何评估优化对站点用户的好处? 以及为什么您需要考虑这些指标?

我们将尝试通过使用create-react-app (CRA) 工具来查看创建React应用程序的最简单方法,以回答这些问题。 使用此工具创建的新项目具有以下依赖性:

  • 主要的React库,它允许您使用React组件:2.5 Kb。
  • react-dom库允许将组件显示在页面上,并将它们转换为适合插入DOM树的结构:30.7 Kb。
  • 少量代码,其中包括第一个组件的模板:大约3 Kb。

此数据是从React 16.6.3获得的。

为了确定在Moto G4手机上下载新的CRA应用程序将花费多长时间,可以使用WebPageTest服务。


使用不同的网络在Moto G4上的网站加载时间

此应用程序是“ Hello,World”的变体,托管在Firebase托管上,正在研究使用三种类型的连接将其加载到Chrome浏览器中:

  • 4G(9 Mbps)
  • 3G(1.6 Mbps)
  • 慢速3G连接(400 Kbps)

在这里,您需要考虑网络延迟。

为什么在实验中使用Moto G4? 这是一款简单的廉价电话,类似于发展中国家许多人使用的基本设备形式的电话。 在4G网络上,应用程序在2秒内加载完毕。 在速度较慢的3G网络中,页面在线花费了超过4秒的时间。

尽管这些指标非常有趣,但是如果您不知道用户是谁,它们就不是特别有用。 您所定义的“慢”可能与我或其他人认为“慢”的完全不同。 使用的设备和网络连接可能会扭曲您对网站“快速”加载的看法。 如果在此实验中包括台式计算机,该台式计算机通过有线连接连接到Internet,则可以看到“快速”和“慢速”加载站点之间的巨大区别。


在台式机和Moto G4上的网站加载时间

为了提高使用React库构建的React应用程序的性能,正如他们所说的那样,它们是现成的,概述了React DOM的改进,旨在简化某些事情。 因此,事件系统包含许多polyfill,对于许多新的浏览器而言,这些不需要,并且开发团队正在考虑选择删除或简化它们的选项。 您可以在这里观看。

我可以衡量当前的网站性能水平吗?


一个典型的React应用程序可以包含许多第三方组件和库。 这意味着“ Hello,World”应用程序的性能不能为我们提供有关实际应用程序如何加载的特别有价值的信息。 有没有办法找出使用某些技术(例如React)构建的大多数站点的高性能?

HTTP存档资源可以帮助我们回答这个问题。 这是一个开放源代码平台,专注于观察Web的构建方式。 这是通过每月爬网数百万个站点,使用WebPageTest分析它们并记录有关它们的信息来完成的。 该信息包括查询的数量,有关数据加载的度量,传输的数据的大小以及其他指示符。

这是另一个有趣的工具-Chrome的扩展程序,称为Library-Detector-for-Chrome 。 它使您能够确定页面上使用了哪些JavaScript库。 最近,它已被包含在Lighthouse中作为页面审核工具。 这表明可以为许多站点(其信息存储在HTTP存档中)获取此扩展提供的信息。 这可以帮助那些想要分析使用特定JavaScript库的数千个站点的测试结果的人(确定React的机制在此处 )。

完整的HTTP Archive数据集是公开可用的,可以在BigQuery上找到。 在使用React研究了140,000个站点并将其加载到人工模拟的移动环境中之后(数据集2019_01_01 ),我们设法找到了以下内容:

  • “第一个有意义的绘制”指示器的中位数(第一个有效显示,绘制重要元素的时间)为6.9 s。
  • 互动时间指示器的中位数(互动时间,互动元素的加载时间)为19.7 s。

您可以自己浏览这些数据。

用户开始与该站点进行交互大约需要20秒钟。 总的来说,这似乎正在发生,尽管现在看来似乎难以置信。 在大型站点上,通过慢速通信线路上的弱设备与它们合作时,可以看到这一点。 另外,现在我想提出几个理由,为什么应该对这些数据表示怀疑。

  • 有许多因素会影响站点性能。 其中包括-发送给用户的JavaScript代码数量,页面上显示的图像和其他资料的数量,等等。 如果不考虑其他因素,它将错误地将任何基于React的站点的性能与标记为“ Hello,World”的页面的性能进行比较。
  • 如果您尝试通过替换React请求中其他某个库的名称来获取数据,则会得到非常相似的数字。

为了获得有关使用某种库实际演示的站点性能的准确数据,需要完成许多工作。

JavaScript代码增长


现代网站的常见问题(不限于特定的库)与客户端浏览时通常必须加载的JavaScript代码量有关。 HTTP存档已经对此做了很好的说明。 简而言之,以下是从不同年份从网站下载的JavaScript量的中位数:

  • 74.7 Kb-移动网页,2011年12月15日。
  • 384.4 Kb-移动网页,2018年12月15日。

应该记住的是,该数据是在处理数百万页后获得的。 可能有成千上万的非典型站点使该指标失真。 这是一个可行的想法。 因此,我们将尝试找出该指标如何查找Alexa排名中的前10,000个网站:

  • 381.5 Kb-移动网页,2018年12月15日(此处是请求 )。

所有这些使我们得出的结论是,如今创建的网站比几年前创建的网站包含更多的JS代码。 这是一个重要的观察。 网站的规模越来越大,它们的互动性和复杂性也越来越高,并且这些网站的JS代码的数量每年都在逐渐增长。 您可能已经听说过这一点,但是发送给浏览器的JavaScript代码越多,解析,编译和执行它所花费的时间就越多。 结果,这使站点变慢。

重要的是要注意,每个站点以及每个站点的用户群都是唯一的。 许多开发人员的站点包含超过300 KB的JS代码,他们没有面对大多数用户都遇到性能问题的事实,这是完全正常的。 但是,如果您担心用户可能难以查看您的React网站,为了评估实际情况,最好从分析开始。

页面分析和分析


可以从两个角度查看分析和分析React应用程序:

  • 首先,它是关于评估组件的性能。 这会影响用户与网站的交互方式。 例如,如果通过单击按钮显示列表,则应快速完成此操作,但如果在此操作过程中重新渲染了数百个组件,即使没有必要,则该操作将被认为很慢。
  • 其次,我们正在讨论应用程序要多久才能进入工作状态。 也就是说,在开始加载网站后有多少时间,用户将能够与他进行交互。 在网站首页加载期间发送给用户的代码量是影响用户可以开始使用应用程序的速度的一个因素示例。

性能评估和组件优化


让我们尝试用一句话来表达React对帐算法的含义,或所谓的“虚拟DOM”的本质。 它看起来像这样:“ React采取步骤在新的DOM树和旧的树之间进行区分,以便了解当组件中的数据发生更改时应在用户界面中准确更新的内容。” 与每次状态或属性更改时重新呈现整个应用程序相比,这给系统带来的负载要小得多( 在这里您可以了解O(n 3 )和O(n)之间的差异)。 这是 Dan Abramov的文章,您可以在其中找到有关和解的说明。

即使考虑到这些优化都已内置在React的内部机制中这一事实,当应用程序中的组件在不应该发生的情况下重复呈现时,总会遇到问题。 在小型应用程序中,这可能不会引起注意,但会严重影响在页面上显示数百个组件的应用程序的性能。

出于许多原因,进行了不必要的组件重新渲染。 例如,在组件内部工作的功能可能没有达到预期的效果,或者当仅将一个新元素添加到此列表时,可能会重绘整个组件列表。 您可以使用一些工具来找出哪些组件树已经渲染太长时间了。 其中,应注意以下几点:

  • Chrome开发者工具仪表板。
  • React开发人员工具探查器。

using使用Chrome Developer Tools性能面板进行性能分析


React使用用户计时API来衡量组件生命周期每一步所花费的时间。 可以使用Chrome开发者工具收集和分析React应用程序的性能信息。 这使您能够了解在用户与页面交互或重新加载组件时,组件在页面上的连接,显示和断开的效率如何。


组件性能分析

这是有关此主题的一些很好的资料,致力于研究使用Chrome开发人员工具使用React 16编写的应用程序的性能。

用户计时API仅在开发期间使用。 生产中将其关闭。 在这种情况下,可以使用这种机制的更快实现,而不会严重影响性能。 对这种机制的需求成为开发较新的API Profiler的原因之一。

using使用React开发人员工具中的事件探查器进行性能分析


随着React开发人员工具中react-dom 16.5库的发布,可以使用一个名为Profiler的新面板。 它使您可以分析组件渲染的性能。 这是使用Profiler API完成的,该API收集有关重新渲染的所有组件的操作执行时间的信息。

Profiler面板是React开发人员工具中的独立选项卡。 在这里,与“ Chrome开发者工具”工具栏的“性能”面板一样,您可以记录有关用户操作和页面重新加载的信息,以收集用于分析组件性能的数据。


使用React Developer Tools收集数据

数据收集完成后,将显示所谓的“火热图”,显示在页面上呈现组件所需的时间。


火焰分析器时间表

在这里,您可以在添加,删除或更新DOM节点时在不同的提交或状态之间进行切换。 这使您可以获得有关在组件的各种操作上花费了多少时间的更多信息。


在事件探查器中查看提交信息

此处显示的屏幕快照表示通过记录在简单应用程序中执行的用户操作而获得的数据。 单击按钮后,该应用程序将下载趋势GitHub存储库的列表。 如您所见,这里只有两个提交:

  • 一个是用于加载指示符,在加载项目列表时显示。
  • 另一个表示对API的调用完成且列表显示在DOM中的时刻。

右侧的图显示了有用的元数据,其中包括提交信息或组件数据,例如属性和状态。


元数据

使用React Profiler时,您还可以查看由各种图形表示的其他数据。 您可以从React博客的这篇文章中了解有关React Profiler的更多信息。

为了使我们的示例复杂一点,请考虑类似的情况,但是现在我们将执行许多API调用,以使用不同的编程语言(例如Golang,JavaScript等)下载趋向于存储库。 如您所料,采用这种方法,我们可以处理更多的事务。


增加提交次数,同时使应用程序的使用方案复杂化

以后的提交以更长的时间表为特色,它们的颜色更多。 这意味着随着页面上元素列表的大小增加,所有组件完成渲染所需的时间也增加。 这是由于以下事实:列表中的每个组件都会随着对API的每次新调用而重新呈现。 这有助于确定可以很容易解决的问题。 解决方案是,将新项目添加到列表时,无需再次呈现列表中的项目。

▍尽量减少不必要的组件重新渲染操作


有很多方法可以消除不必要的操作来重新渲染React组件,或者至少可以减少此类操作的数量。 让我们考虑其中的一些。

  • 您可以使用shouldComponentUpdate()的组件生命周期方法:

     shouldComponentUpdate(nextProps, nextState) { // true        } 
  • 您可以使用PureComponent构造基于类的组件

     import React, { PureComponent } from 'react'; class AvatarComponent extends PureComponent { } 
  • 对于功能组件,可以使用memo

     import React, { memo } from 'react'; const AvatarComponent = memo(props => { }); 
  • 您可以记住Redux选择器(例如,使用reselect )。
  • 您可以使用虚拟化(例如,使用react-window )来优化很长列表的输出。

在这里那里 -几个有用的视频,讨论了使用React Profiler查找应用程序瓶颈的方法。

性能评估和应用优化


除了分析DOM突变和组件重新渲染操作外,还有其他更高层次的现象值得探索。 要全面评估站点性能,可以使用Lighthouse

使用Lighthouse可以通过三种方法来测试网页:

  • 使用Node.js命令行界面
  • 使用Chrome 扩展程序
  • 使用Chrome开发者工具的“审核”工具栏。

这是“审计”面板中Lighthouse的外观。


审核面板上的灯塔

Lighthouse通常不需要大量时间从页面收集他需要的所有数据并对该数据执行许多检查。 这些操作完成后,Lighthouse将显示一个包含摘要信息的报告。

为了了解将页面加载到浏览器中会涉及到加载太多的JavaScript代码,并得出减少该代码量的结论,您需要注意报告中的以下短语:

  • 消除渲染阻止资源
  • JavaScript启动时间太长
  • 避免大量的网络负载

如果Lighthouse因为页面使用了太大的JS捆绑软件而报告了这些问题,则作为解决问题的一种方法,首先值得考虑的是拆分捆绑软件。 事实是,如果可以将代码分为多个片段,而某些片段仅在处理网站的某些页面时才需要,那么我们没有理由不使用此机会。

▍JS包分离


将代码分成几部分的一种方法是使用动态导入:

   import('lodash.sortby')   .then(module => module.default)   .then(module => doSomethingCool(module)) 

导入语法看起来像一个函数调用,但是它允许您使用promises机制异步导入任何模块。 在此示例中,首先从lodash库中导入sortby方法,然后执行doSomethingCool()方法。


申请号码排序

在此的示例发生以下情况:

  1. 用户单击按钮以对三个数字进行排序。
  2. 导入lodash.sortby
  3. doSomethingCool()方法,该方法对数字进行排序并将其显示在新的DOM节点中。

这是一个非常简单的人工示例,因为如果某人需要对网页上的数字进行排序,那么他可能只会使用Array.prototype.sort()方法。 , , .

, JavaScript TC39. Chrome Safari , Webpack , Rollup Parcel .
React, , . — React.lazy :

 import React, { lazy } from 'react'; const AvatarComponent = lazy(() => import('./AvatarComponent')); 

, , , . Suspense , , «» . React.lazy , , , :

 import React, { lazy, Suspense } from 'react'; import LoadingComponent from './LoadingComponent'; const AvatarComponent = lazy(() => import('./AvatarComponent')); const PageComponent = () => ( <Suspense fallback={LoadingComponent}>   <SomeComponent /> </Suspense> ) 

Suspense . React-, , , , React, loadable-components .

 import React from 'react'; import loadable from '@loadable/component' import LoadingComponent from './LoadingComponent'; const AvatarComponent = loadable(() => import('./AvatarComponent'), {   LoadingComponent: () => LoadingComponent }); 

LoadingComponent loadable-component .

, , loadable-components , - .

, . , , . React , React Suspense .

▍ , ?


, react-loadable-visibility , loadable-components -API Intersection Observer. , .

 import React from 'react'; import loadableVisibility from 'react-loadable-visibility/loadable-components' const AvatarComponent = loadableVisibility(() => import('./AvatarComponent'), { LoadingComponent: Loading, }) 

▍ ,


- — -, . - , , . , , , -, -, , .





Workbox
— , -, . CRA 2.0 , , src/index.js . - .

   import React from 'react'; //... //   ,          //    ,     // unregister()  register().    ,     - //    . //     : http://bit.ly/CRA-PWA serviceWorker.unregister(); 

- Workbox .


- HTML-, , , . , , , , , JS-, .

, , DOM-, , , (, hydrate() React). , , , , .

React 16, . renderToString() HTML-, renderToNodeStream() Readable- Node.

HTML- , . , , .

React , , , renderToStaticNodeStream .

▍ ,


- , , , , - , , . , , .

, , , . HTML- , .

, react-snap , Puppeteer .

, .

▍ , CSS-in-JS


React-, , CSS-in-JS- emotion styled-components . , , , , , . CSS-in-JS , , , . , , JavaScript- , , . , , , .


, ( )

- , . emotion styled-components . Readable- Node. Glamor , .

, .


Preact-, ( )

, CSS-in-JS — , , , , , astroturf . , , , , .


«», , . , , .

, Lighthouse. , React-, React A11y .

, react-axe , , JSX-.


? -, , ? , ?

, - . «» . , — , .

create-react-app -:

 { "short_name": "React App", "name": "Create React App Sample", "icons": [   {     "src": "favicon.ico",     "sizes": "64x64 32x32 24x24 16x16",     "type": "image/x-icon"   } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } 


, . , , . — . 为什么会这样呢? , , .

▍Atomic CSS


CSS- (Atomic CSS) , . , , :

 <button class="bg-blue">Click Me</button> 

padding :

 <button class="bg-blue pa2">Click Me</button> 

依此类推。 class DOM, , , CSS-, . , . Tachyons .

. tachyon-components API, API styled-components :

 import styled from 'tachyons-components' const Button = styled('button')` bg-blue pa2 ` <Button>Click Me</Button> 

, , CSS- «» , CSS-, , .


, , . — :

 import { useState } from 'react'; function AvatarComponent() { const [name, setName] = useState('Houssein'); return (   <React.Fragment>     <div>       <p>This is a picture of {name}</p>       <img align="center" src="avatar.png" />     </div>     <button onClick={() => setName('a banana')}>       Fix name     </button>   </React.Fragment> ); } 

:


, . , , recompose .

React Conf, .

, , JavaScript- . , React , 1.5 ( ). , - , , , , , , , .

总结


, React-. , , :

  1. , :

    • Performance Chrome.
    • React.


    • , , shouldComponentUpdate() .
    • , , PureComponent .
    • React.memo .
    • Redux (, reselect ).
    • (, react-window ).
  2. Lighthouse.
  3. , :

    • React.lazy .
    • loadable-components .
    • - , . Workbox .
    • — ( renderToNodeStream renderToStaticNodeStream ).
    • ? react-snap .
    • , CSS-in-JS.
    • , . React A11y react-axe .
    • - , , , .

, React, , :

  • API, , .
  • , , , .

« » . -, .

亲爱的读者们! React-?

Source: https://habr.com/ru/post/zh-CN442650/


All Articles