渐进式React

更新日期: 2019-04-02阅读: 2.5k标签: react

可以说 react 为Web开发者带来了全新的开发模式,而在各类新功能下,如何达到性能最优仍是我们需要关心的。今天做一次精读尝试,原文地址在文末,话不多说,先呈上一份性能清单:

1. 测量组件级渲染性能

  • Chrome DevTools Performance 面板
  • React DevTools profiler 面板

2. 避免非必要的组件重复渲染

  • 尽量使用shouldComponentUpdate
  • Class 组件使用PureComponent
  • 功能组件使用React.memo
  • 记住 Redux selectors(比如使用reselect
  • 虚拟化超长列表(比如使用react-window

3. 使用 Lighthouse 测量App级性能
4. 提升APP级性能

  • 如果没有使用服务端渲染,则使用React.lazy分割组件
  • 如果使用了服务端渲染,则使用loadable-components之类的库来分割组件
  • 使用 service worker 来缓存需要的文件,Workbox 可以帮到你
  • 如果使用了服务端渲染,使用流式传输(使用renderToNodeStream或renderToStaticNodeStream)
  • 无法使用 SSR?使用react-snap等方案进行预渲染(Pre-render)
  • 如果用到 css-in-JS 库,将关键路径样式解析出来
  • 保障应用可用性,考虑使用React A11y或react-axe等库
  • 如果用户需要通过设备主屏幕访问站点,增加 web app manifest

对于 React 应用,我们主要关注两个性能维度:组件渲染性能 与 页面加载性能,由于 React 的核心在于组件设计,那先从组件性能讲起。


测量组件级性能

React 熟为人知的“Virtual dom”,是建立在高效调和(reconciliation)算法基础上的,其基于一定约定假设,将虚拟 DOM Diff 时间复杂度从O(n3)降为O(n)。虽然这些 React 内部实现不要求大家都理解,在小型应用中性能也不足以成为瓶颈,但性能优化本来就是量变到质变的过程,因此让我们从测量组件性能工具做起。


使用 Chrome 开发者工具测量性能

React 使用 User Timing API 收集各生命周期耗时,为避免测量本身带来的性能影响,性能采集仅在开发模式有效。

说实话,这类火焰图在视觉上有很强直观性,但缺少的有效调试信息,因此 React Devtools 提供了更为强大的能力。


使用 React DevTools Profiler 分析性能

React 16.5 开始使用 Profiler api 收集组件渲染耗时,以独立Tab形式呈现在 React DevTools 中。它的使用类似于 Chrome DevTools Performance,通过录制来决定收集数据范围。

React DevTools Profiler 示例

相比 Chrome DevTools Performance 中呈现的 Timing 信息,React DevTools Profiler 提供了更多辅助定位性能瓶颈的组件级信息,这里简单说下几个亮点:

  1. 以 commit 维度记录信息。熟悉 React 内部原理的同学知道,React 生命周期中有个 Commit 阶段,React DevTools Profiler 会以每次 commit 维度记录渲染相关信息,在右侧进行展示。
  2. 具体组件状态信息。左侧的火焰图对应了组件层级结构,以不同颜色区分组件渲染次数,高亮重复渲染的组件。点击组件后,右侧会展示组件具体渲染次数,以及当时的 state 与 props。
  3. 简单的统计能力。除了火焰图,工具还有排名(Ranked)和交互(Interactions)两个维度统计,帮助更快的定位组件瓶颈。

总体上 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


测量 App 级性能

除了 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 已经率先支持了,WebpackRollup 和 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 的浪潮中,你的站点值得拥有。


流式 SSR

为了加快页面呈现,服务端渲染概念已经被大家接受和使用。为了最大限度复用服务端返回的 html,React 还提供了 hydrate() API。这时优化的目光投向了 TTI,流式渲染也应运而生,相对之前的renderToString API 返回 HTML 字符串,renderToNodeStream会返回 NodeReadable字节流。这样浏览器就能源源不断地获取到页面块,hydrate API 也很好地支持了流式处理,真的很强大。

关于 SSR 更多信息,可以查看本专栏的《Web渲染那些事儿》


SSR 不行?预渲染来顶

其实服务端渲染是个笼统的概念,由于现代页面大多都是动态的,因此每个请求可能都要在服务器上处理一遍。然而纯服务端渲染与纯客户端渲染之间,是存在中间地带的。虽然页面是通过组件模式进行开发,但页面内容可能是静态的,只要生成一次就行,这就是预渲染(Prerendering)或静态渲染的由来。

这里介绍一个基于 Puppeteer 的预渲染方案 react-snap,它能让你更简单地进行预渲染页面。


提取关键 CSS-in-JS 样式

出于各种原因,有些开发者会使用 emotion 及 styled-components 等 CSS-in-JS 库,但如果不注意,会导致样式都在运行时解析,也就是导致页面会闪过无样式的瞬间。如果在移动设备或弱网络场景下,体验就很糟糕。上面提到的 SSR 更是如此,因为在客户端JS加载之前,SSR 返回的无样式 DOM 已经开始渲染了。

优化的做法就是将这些关键样式提取出来,好在 emotion 和 styled-components 都原生支持将样式提取到可读流中,流式 SSR 也不用担心闪屏情况了。


杂项

接下几项关于提升开发者体验,并助于减少繁琐的编码。

编写更少代码 = 传输更少代码 = 更快的网页加载


原子 CSS

原子样式的理念是定义单一作用的 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大小,还是看场景应用吧。


Hooks

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 这样拥有广泛开发者的开源项目,有两样事可以期待:

  1. 优化其 API,令构建应用更加容易
  2. 开源社区贡献第三方库,令构建应用更加容易

“令构建应用更加容易”可以指很多方面,让开发者做的更少、页面性能更高是其中之一。

链接: https://fly63.com/article/detial/2655

如何优雅的设计 React 组件

如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。

React深度编程:受控组件与非受控组件

受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。

React框架学习_关于React两种构建应用方式选择

一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。

Gatsby.js_一款基于React.js静态站点生成工具

Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。

React创建组件的三种方式及其区别

React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件

react生命周期详解_深入理解React生命周期

React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。

React + Webpack 构建打包优化

React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大

react router中页面传值的三种方法

这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state

react 高阶组件的 理解和应用

react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)

react中的refs属性的使用方法

React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!