渐进式React

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

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

站长推荐

1.阿里云: 本站目前使用的是阿里云主机,安全/可靠/稳定。点击领取2000元代金券、了解最新阿里云产品的各种优惠活动点击进入

2.腾讯云: 提供云服务器、云数据库、云存储、视频与CDN、域名等服务。腾讯云各类产品的最新活动,优惠券领取点击进入

3.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

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

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

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

React 新 Context API

React 新 Context API它更符合工程化, 不再是实验性的,现在它是一流的API! 并且它还使用了 RENDER PROP!你在react官网上听说过 context API?那么你为何要使用context?Context的重生

React + Webpack 构建打包优化

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

react-navigation 监听顶部导航栏点击/滑动状态

使用createMaterialTopTabNavigator创建顶部导航栏,希望实现切换到指定的Tab再获取数据,查看官方文档只能找到tabBarOnPress 方法监听点击回调,但是无法监听滑动切换

新手学习 react 迷惑的点

网上各种言论说 React 上手比 Vue 难,可能难就难不能深刻理解 JSX,或者对 ES6 的一些特性理解得不够深刻,导致觉得有些点难以理解,然后说 React 比较难上手,还反人类啥的

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

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

React中富文本编辑器的技术选型调研

富文本编辑器是项目中不可或缺的部分,目前市面上可以选择的富文本编辑器种类繁多,如何在项目中选择一款集轻量,美观,稳定,坑少,满足需求的富文本编辑器变成了团队中一个重要的问题。我对这两款富文本编辑器都进行了使用,并结合目前的项目需求进行了比较:react-quill、braft-editor

React新Context API在前端状态管理的实践

众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着React 16.3的发布,新context api成为了新的选择。

用思维模型去理解 React

我了解到,掌握了某种语言、框架或工具的人与没有掌握的人之间的最大区别在于他们所使用的思维模型(Mental Model)。前者拥有清晰而先进的思维模型,而后者则没有。

使用react-app-rewired和customize-cra对默认webpack自定义配置

最近在学习react框架,之前一直都是用vue 开发,知道在vue 中知道如何配置一下相关的webpack 有助于开发,学react 过程中,我也在想这些该怎么配置啊,所以就有这篇文章

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

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

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