| 导语 本篇干货是接本周三React 16 加载性能优化指南(上)推文。
关于 React 应用加载的优化,其实网上类似的文章已经有太多太多了,随便一搜就是一堆,已经成为了一个老生常谈的问题。
但随着 React 16 和 Webpack 4.0 的发布,很多过去的优化手段其实都或多或少有些“过时”了,而正好最近一段时间,公司的新项目迁移到了 React 16 和 Webpack 4.0,做了很多这方面的优化,所以就写一篇文章来总结一下。
三、首次内容渲染 -> 可交互
这一段过程中,浏览器主要在做的事情就是加载及初始化各项组件
大多数打包器(比如 webpack、rollup、browserify)的作用就是把你的页面代码打包成一个很大的 “bundle”,所有的代码都会在这个 bundle 中。但是,随着应用的复杂度日益提高,bundle 的体积也会越来越大,加载 bundle 的时间也会变长,这就对加载过程中的用户体验造成了很大的负面影响。
为了避免打出过大的 bundle,我们要做的就是切分代码,也就是 Code Splitting,目前几乎所有的打包器都原生支持这个特性。
Code Splitting 可以帮你“懒加载”代码,以提高用户的加载体验,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个 bundle 拆分成单个 bundle + 多份动态代码的形式。
比如我们可以把下面这种形式:
import { add } from './math';
console.log(add(16, 26));
改写成动态 import 的形式,让首次加载时不去加载 math
模块,从而减少首次加载资源的体积。
import("./math").then(math => {
console.log(math.add(16, 26));
});
React Loadable 是一个专门用于动态 import 的 React 高阶组件,你可以把任何组件改写为支持动态 import 的形式。
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.
相关文章:《Deploying ES2015+ Code in Production Today》
如今大多数项目的做法都是,编写 ES2015+ 标准的代码,然后在构建时编译到 ES5 标准运行。
比如一段非常简洁的 class 语法:
class Foo extends Bar {
constructor(x) {
super()
this.x = x;
}
}
会被编译成这样:
"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">
的浏览器,必然支持下面的特性:
而不支持 <script type="module">
的老旧浏览器,会因为无法识别这个标签,而不去加载 ES2015+ 的代码。另外老旧的浏览器同样无法识别 nomodule
熟悉,会自动忽略它,从而加载 ES5 标准的代码。
简单地归纳为下图:
根据这篇文章,打包后的体积和运行效率都得到了显著提高。
这个阶段就很简单了,主要是各种多媒体内容的加载
懒加载其实没什么好说的,目前也有一些比较成熟的组件了,自己实现一个也不是特别难:
当然你也可以实现像 Medium 的那种加载体验(好像知乎已经是这样了),即先加载一张低像素的模糊图片,然后等真实图片加载完毕之后,再替换掉。
实际上目前几乎所有 lazyload 组件都不外乎以下两种原理:
我们在加载文本、图片的时候,经常出现“闪屏”的情况,比如图片或者文字还没有加载完毕,此时页面上对应的位置还是完全空着的,然后加载完毕,内容会突然撑开页面,导致“闪屏”的出现,造成不好的体验。
为了避免这种突然撑开的情况,我们要做的就是提前设置占位元素,也就是 placeholder:
已经有一些现成的第三方组件可以用了:
另外还可以参考 Facebook 的这篇文章:《How the Facebook content placeholder works》
这篇文章里,我们一共提到了下面这些优化加载的点:
实际上可优化的点还远不止这些,这里推荐一些相关资源给大家阅读:
希望这篇文章,能拯救你下半年的 KPI :)
--------------------------------------------------------------------------
原文作者:腾讯工程师王伟嘉。
来源:腾讯内部KM论坛。
想玩转React?
想让下半年的KPI蹭蹭蹭的往上涨?
React实践宝典等你来撩!
前端NEXT学位-React课程火热招生中!
感兴趣的同学赶紧点击原文了解详情吧~
腾讯NEXT学位
求职干货 | 前辈blog | 前端课程