渐进式React

时间: 2019-04-02阅读: 252标签: 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. 开源社区贡献第三方库,令构建应用更加容易

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

链接: http://www.fly63.com/article/detial/2655

React中编写CSS的姿势

在任何环境之下其实没有最佳,最有最适合,那么在React中编写CSS也是类似的。在React中有很多编写CSS的方式,在社区中讨论最多的应该是CSS In JS 和 CSS Modules

react如何通过shouldComponentUpdate来减少重复渲染

在react开发中,经常会遇到组件重复渲染的问题,父组件一个state的变化,就会导致以该组件的所有子组件都重写render,尽管绝大多数子组件的props没有变化

react-redux 的使用

类似于 Vue,React 中组件之间的状态管理 第三方包为:react-redux。react-redux 其实是 Redux的官方React绑定库,它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据。

21 项优化 React App 性能的技术

在 React 内部,React 会使用几项巧妙的小技术,来优化计算更新 UI 时,所需要的最少的更新 DOM 的操作。在大多数情况下,即使你没有针对性能进行专项优化,React 依然很快,但是仍有一些方法可以加速 React 应用程序

React使用propTypes进行类型检查

注意: React.PropTypes 自 React v15.5 起已弃用。请使用 prop-types 库代替。随着你的应用的开发,你会使用类型检查的方法来捕获很多bug。对于一些应用,你可以使用js扩展就像Flow或者TypeScript去对整个应用进行类型检查

为什么说React 16是开发者的福音?

就像人们对更新移动应用程序和操作系统感到兴奋一样,开发人员也应该对更新框架感到兴奋。不同框架的新版本具有新特性和开箱即用的技巧。下面是将现有应用程序从 React 15 迁移到 React 16 时应该考虑的一些好特性。

React ref

React ref理解:通过指定ref获得你想操作的元素,然后进行修改.是当组件挂载后和卸载后,以及ref属性本身发生变化时,回调函数就会被调用。 因为ref引用的是组件的实例

函数式的React

React 是现在最流行的 JavaScript 库之一。使用 React 可以非常轻松地创建 Web 用户交互界面。 它的成功有很多因素,但也许其中一个因素是清晰有效的编程方法。在 React 的世界中,UI 是由一个一个组件所组成的。

react hook超实用的用法和技巧分析

react hook发布也已经有几个月了,相信有部分人已经开始使用了,还有些人在犹豫要不要用,可能更多人安于现状,没有要用的打算,甚至还有很多公司的react版本是15或以下的,迫于升级的难度没有使用。以我个人的观点,要不要使用react hook呢?

react中PureComponent浅对比策略

PureComponent实现了Component中没有实现的shouComponentUpdata()方法,会对state和props进行一次浅对比,本文介绍一下浅对比策略,源码中,实现浅对比的函数是:shallowEqual()

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

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全