可以说 react 为Web开发者带来了全新的开发模式,而在各类新功能下,如何达到性能最优仍是我们需要关心的。今天做一次精读尝试,原文地址在文末,话不多说,先呈上一份性能清单:
1. 测量组件级渲染性能
2. 避免非必要的组件重复渲染
3. 使用 Lighthouse 测量App级性能
4. 提升APP级性能
对于 React 应用,我们主要关注两个性能维度:组件渲染性能 与 页面加载性能,由于 React 的核心在于组件设计,那先从组件性能讲起。
React 熟为人知的“Virtual DOM”,是建立在高效调和(reconciliation)算法基础上的,其基于一定约定假设,将虚拟 DOM Diff 时间复杂度从O(n3)降为O(n)。虽然这些 React 内部实现不要求大家都理解,在小型应用中性能也不足以成为瓶颈,但性能优化本来就是量变到质变的过程,因此让我们从测量组件性能工具做起。
React 使用 User Timing API 收集各生命周期耗时,为避免测量本身带来的性能影响,性能采集仅在开发模式有效。
说实话,这类火焰图在视觉上有很强直观性,但缺少的有效调试信息,因此 React Devtools 提供了更为强大的能力。
React 16.5 开始使用 Profiler API 收集组件渲染耗时,以独立Tab形式呈现在 React DevTools 中。它的使用类似于 Chrome DevTools Performance,通过录制来决定收集数据范围。
React DevTools Profiler 示例
?
相比 Chrome DevTools Performance 中呈现的 Timing 信息,React DevTools Profiler 提供了更多辅助定位性能瓶颈的组件级信息,这里简单说下几个亮点:
总体上 Profiler 工具使用简单,没什么门槛,接下来介绍优化组件渲染的相关技术。
去除无用的重复渲染,方案因场景各异:
使用 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// 仅在确定条件下返回 true
}
Class 组件使用 PureComponent
import React, { PureComponent } from ‘react‘;
class AvatarComponent extends PureComponent {
}
功能组件使用 memo
import React, { memo } from ‘react‘;
const AvatarComponent = memo(props => {
});
记住 Redux selectors(比如使用 reselect)
虚拟化超长列表(比如使用 react-window)
除了 DOM 级的渲染性能,还有更高层面的应用加载性能需要关注。这方面的性能工具属 Lighthouse 最有名了,我们可以通过 Node CLI、Chrome 扩展和 Chrome DevTools 的 Audits 面板用到它。
Lighthouse 根据一系列性能规则,对目标页面进行检查,最终生成一份性能报告,给出未达标指标的改进建议。在 React 项目中,随着路由和组件的膨胀,很容易触发 Lighthouse 对 JavaScript 传输体积的检查规则(Avoid enormous network payloads)。在实践中,已有成熟的方案供我们使用——代码分割。
进行代码分割的一个方法是动态导入(dynamic imports):
import(‘lodash.sortby‘)
.then(module => module.default)
.then(module => doSomethingCool(module))
这里的 import 语法像是函数调用,允许异步加载模块并通过 Promise 返回。上面代码动态获取了 lodash sortby 方法,紧接着被后续代码使用。
虽然动态导入目前仍处于 stage 3 阶段,Chrome and Safari 已经率先支持了,Webpack、Rollup 和 Parcel 也做好了支持。
回到 React,组件级别的代码分割已经被良好地抽象,比如React.lazy:
import React, { lazy } from ‘react‘;
const AvatarComponent = lazy(() => import(‘./AvatarComponent‘));
然而这么做可能会导致用户可感知的加载延迟。对此,可以将Suspense组件配合React.lazy一起使用,“暂停”部分组件的渲染,通过渲染 Loading 组件,对仍在加载的组件进行降级处理:
import React, { lazy, Suspense } from ‘react‘;
import LoadingComponent from ‘./LoadingComponent‘;
const AvatarComponent = lazy(() => import(‘./AvatarComponent‘));
const PageComponent = () => (
<Suspense fallback={LoadingComponent}>
<AvatarComponent />
</Suspense>
)
Suspense 还不支持 SSR,如果要在服务端渲染使用代码分割,可以使用loadable-components这样的库。另外如果需要在滚动场景做异步加载的同学,可以了解下 react-loadable-visibility。
Service Worker 就不重新介绍了,概括起来就是一个运行在浏览器后台的可编程代理,让我们对网络缓存更加可控。一个具体的使用场景是,通过控制缓存策略,来提升用户二次访问时的页面加载体验。
这里主要是安利 Workbox 这个工具包,它能让我们更简单地使用 Service Worker,具体细节不做展开,在 PWA 的浪潮中,你的站点值得拥有。
为了加快页面呈现,服务端渲染概念已经被大家接受和使用。为了最大限度复用服务端返回的 html,React 还提供了 hydrate() API。这时优化的目光投向了 TTI,流式渲染也应运而生,相对之前的renderToString API 返回 html 字符串,renderToNodeStream会返回 NodeReadable字节流。这样浏览器就能源源不断地获取到页面块,hydrate API 也很好地支持了流式处理,真的很强大。
关于 SSR 更多信息,可以查看本专栏的《Web渲染那些事儿》。
其实服务端渲染是个笼统的概念,由于现代页面大多都是动态的,因此每个请求可能都要在服务器上处理一遍。然而纯服务端渲染与纯客户端渲染之间,是存在中间地带的。虽然页面是通过组件模式进行开发,但页面内容可能是静态的,只要生成一次就行,这就是预渲染(Prerendering)或静态渲染的由来。
这里介绍一个基于 Puppeteer 的预渲染方案 react-snap,它能让你更简单地进行预渲染页面。
出于各种原因,有些开发者会使用 emotion 及 styled-components 等 CSS-in-JS 库,但如果不注意,会导致样式都在运行时解析,也就是导致页面会闪过无样式的瞬间。如果在移动设备或弱网络场景下,体验就很糟糕。上面提到的 SSR 更是如此,因为在客户端JS加载之前,SSR 返回的无样式 DOM 已经开始渲染了。
优化的做法就是将这些关键样式提取出来,好在 emotion 和 styled-components 都原生支持将样式提取到可读流中,流式 SSR 也不用担心闪屏情况了。
接下几项关于提升开发者体验,并助于减少繁琐的编码。
编写更少代码 = 传输更少代码 = 更快的网页加载
原子样式的理念是定义单一作用的 class,以达到灵活组合样式的目的。看个简单的例子:
<button class="bg-blue">Click Me</button>
bg-blue 定义了蓝色背景色,作用在 button 上可令其应用这条规则。如果要给它加个 padding,可以设置单独负责 padding 的 class:
<button class="bg-blue pa2">Click Me</button>
虽然会多写几个 css class,但可以不用再去编辑复杂的 CSS 文件了,如果你不想自己维护一套样式规范,可以直接用开源的 Tachyons 方案。组件级别还有 tachyons-components 这样的方案,个人觉得还不太成熟,这里不做展开。
整体来看原子 CSS 比较适用于样式风格简单统一的场景,让开发者聚焦 JS 部分,随时修改样式而不用关心样式继承方面的影响,另一个好处是 CSS 可以长期缓存,基本不需要更新。
出于性能考虑,页面首次加载会被统一样式的 CSS 阻塞,看了下gzip后有10KB大小,还是看场景应用吧。
广州品牌设计公司https://www.houdianzi.com PPT模板下载大全https://redbox.wode007.com
Hooks 允许以功能组件实现以前只有class组件才能实现的功能,比如对state的操作:
import { useState } from ‘react‘;
function AvatarComponent() {
const [name, setName] = useState(‘Houssein‘);
return (
<React.Fragment>
<div>
<p>This is a picture of {name}</p>
<img src="avatar.png" />
</div>
<button onClick={() => setName(‘a banana‘)}>
Fix name
</button>
</React.Fragment>
);
}
除了 React 提供的 useState 和 useEffect,可以自定义 hooks 来复用跨组件的逻辑。在此之前要实现该功能,会用到 recompose 这个库,Hooks 出现后就可以退出历史舞台了。(真实情况是 recompose 的作者加入了 React Team,并推出了 Hooks)
虽然 Hooks 的定位是解决代码架构问题,但确实也在加载性能方面做出了贡献。虽然 Hooks 功能相关代码为 React 增加了1.5KB(gzip后),但 Hooks 代码比 class 组件代码更易压缩,因此可以减小一些 JS 包大小。
像 React 这样拥有广泛开发者的开源项目,有两样事可以期待:
“令构建应用更加容易”可以指很多方面,让开发者做的更少、页面性能更高是其中之一。
原文:https://www.cnblogs.com/Qooo/p/14135069.html