前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 16 加载性能优化指南(下)

React 16 加载性能优化指南(下)

作者头像
腾讯NEXT学位
发布2018-10-26 19:23:41
1.6K0
发布2018-10-26 19:23:41
举报
文章被收录于专栏:腾讯NEXT学位

| 导语 本篇干货是接本周三React 16 加载性能优化指南(上)推文。

关于 React 应用加载的优化,其实网上类似的文章已经有太多太多了,随便一搜就是一堆,已经成为了一个老生常谈的问题。

但随着 React 16 和 Webpack 4.0 的发布,很多过去的优化手段其实都或多或少有些“过时”了,而正好最近一段时间,公司的新项目迁移到了 React 16 和 Webpack 4.0,做了很多这方面的优化,所以就写一篇文章来总结一下。

三、首次内容渲染 -> 可交互

这一段过程中,浏览器主要在做的事情就是加载及初始化各项组件


3.1. Code Splitting

大多数打包器(比如 webpack、rollup、browserify)的作用就是把你的页面代码打包成一个很大的 “bundle”,所有的代码都会在这个 bundle 中。但是,随着应用的复杂度日益提高,bundle 的体积也会越来越大,加载 bundle 的时间也会变长,这就对加载过程中的用户体验造成了很大的负面影响。

为了避免打出过大的 bundle,我们要做的就是切分代码,也就是 Code Splitting,目前几乎所有的打包器都原生支持这个特性。

Code Splitting 可以帮你“懒加载”代码,以提高用户的加载体验,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个 bundle 拆分成单个 bundle + 多份动态代码的形式。

比如我们可以把下面这种形式:

代码语言:javascript
复制
import { add } from './math';
console.log(add(16, 26));

改写成动态 import 的形式,让首次加载时不去加载 math 模块,从而减少首次加载资源的体积。

代码语言:javascript
复制
import("./math").then(math => {
  console.log(math.add(16, 26));
});

React Loadable 是一个专门用于动态 import 的 React 高阶组件,你可以把任何组件改写为支持动态 import 的形式。

代码语言:javascript
复制
import Loadable from 'react-loadable';
import Loading from './loading-component';

const LoadableComponent = Loadable({
  loader: () => import('./my-component'),
  loading: Loading,
});
  
export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}

上面的代码在首次加载时,会先展示一个 loading-component,然后动态加载 my-component 的代码,组件代码加载完毕之后,便会替换掉 loading-component

下面是一个具体的例子:

以这个用户主页为例,起码有三处组件是不需要首次加载的,而是使用动态加载:标题栏、Tab 栏、列表。首次加载实际上只需要加载中心区域的用户头像、昵称、ID即可。切分之后,首屏 js 体积从 40KB 缩减到了 20KB.


3.2. 编译到 ES2015+ ,提升代码运行效率

相关文章:《Deploying ES2015+ Code in Production Today》

如今大多数项目的做法都是,编写 ES2015+ 标准的代码,然后在构建时编译到 ES5 标准运行。

比如一段非常简洁的 class 语法:

代码语言:javascript
复制
class Foo extends Bar {
    constructor(x) { 
        super() 
        this.x = x;
    }
}

会被编译成这样:

代码语言:javascript
复制
"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Foo = function (_Bar) {
  _inherits(Foo, _Bar);

  function Foo(x) {
    _classCallCheck(this, Foo);
    
    var _this = _possibleConstructorReturn(this, (Foo.__proto__ || Object.getPrototypeOf(Foo)).call(this));

    _this.x = x;
    return _this;
  } 
    
    return Foo;
}(Bar);

                                                     (可左右滑动浏览)

但实际上,大部分现代浏览器已经原生支持 class 语法,比如 iOS Safari 从 2015 年的 iOS 9.0 开始就支持了,根据 caniuse 的数据,目前移动端上 90% 用户的浏览器都是原生支持 class 语法的:

其它 ES2015 的特性也是同样的情况。

也就是说,在当下 2018 年,对于大部分用户而言,我们根本不需要把代码编译到 ES5,不仅体积大,而且运行速度慢。我们需要做的,就是把代码编译到 ES2015+,然后为少数使用老旧浏览器的用户保留一个 ES5 标准的备胎即可。

具体的解决方法就是 <script type="module"> 标签。

支持 <script type="module"> 的浏览器,必然支持下面的特性:

  • async/await
  • Promise
  • Class
  • 箭头函数、Map/Set、fetch 等等…

而不支持 <script type="module"> 的老旧浏览器,会因为无法识别这个标签,而不去加载 ES2015+ 的代码。另外老旧的浏览器同样无法识别 nomodule 熟悉,会自动忽略它,从而加载 ES5 标准的代码。

简单地归纳为下图:

根据这篇文章,打包后的体积和运行效率都得到了显著提高。


四、可交互 -> 内容加载完毕

这个阶段就很简单了,主要是各种多媒体内容的加载


4.1. LazyLoad

懒加载其实没什么好说的,目前也有一些比较成熟的组件了,自己实现一个也不是特别难:

  • react-lazyload
  • react-lazy-load

当然你也可以实现像 Medium 的那种加载体验(好像知乎已经是这样了),即先加载一张低像素的模糊图片,然后等真实图片加载完毕之后,再替换掉。

实际上目前几乎所有 lazyload 组件都不外乎以下两种原理:

  • 监听 window 对象或者父级对象的 scroll 事件,触发 load;
  • 使用 Intersection Observer API 来获取元素的可见性。

4.2. placeholder

我们在加载文本、图片的时候,经常出现“闪屏”的情况,比如图片或者文字还没有加载完毕,此时页面上对应的位置还是完全空着的,然后加载完毕,内容会突然撑开页面,导致“闪屏”的出现,造成不好的体验。

为了避免这种突然撑开的情况,我们要做的就是提前设置占位元素,也就是 placeholder:

已经有一些现成的第三方组件可以用了:

  • react-placeholder
  • react-hold

另外还可以参考 Facebook 的这篇文章:《How the Facebook content placeholder works》


五、总结

这篇文章里,我们一共提到了下面这些优化加载的点:

  1. 在 HTML 内实现 Loading 态或者骨架屏;
  2. 去掉外联 css;
  3. 缓存基础框架;
  4. 使用动态 polyfill;
  5. 使用 SplitChunksPlugin 拆分公共代码;
  6. 正确地使用 Webpack 4.0 的 Tree Shaking;
  7. 使用动态 import,切分页面代码,减小首屏 JS 体积;
  8. 编译到 ES2015+,提高代码运行效率,减小体积;
  9. 使用 lazyload 和 placeholder 提升加载体验。

实际上可优化的点还远不止这些,这里推荐一些相关资源给大家阅读:

  • GoogleChromeLabs/webpack-libs-optimizations
  • https://developers.google.com/web/fundamentals/performance/get-started/
  • A Pinterest Progressive Web App Performance Case Study
  • A React And Preact Progressive Web App Performance Case Study: Treebo
  • The Cost of JavaScript

希望这篇文章,能拯救你下半年的 KPI :)

--------------------------------------------------------------------------

原文作者:腾讯工程师王伟嘉。

来源:腾讯内部KM论坛。

想玩转React?

想让下半年的KPI蹭蹭蹭的往上涨?

React实践宝典等你来撩!

前端NEXT学位-React课程火热招生中!

  感兴趣的同学赶紧点击原文了解详情吧~

腾讯NEXT学位

求职干货 | 前辈blog  | 前端课程

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯NEXT学位 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 3.1. Code Splitting
  • 3.2. 编译到 ES2015+ ,提升代码运行效率
  • 四、可交互 -> 内容加载完毕
    • 4.1. LazyLoad
      • 4.2. placeholder
      • 五、总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档