React v16.6 版本引入了React.lazy,Suspense的功能,这个功能主要是利用了webpack对es6的import动态载入组件,可以自动实现Code Splitting
Code Splitting就是把代码分成很多个小块(chunk),需要某部分代码的时候再去加载,减小了页面首屏进来加载很大一个js文件的压力,另外拆分成小块还能更好的利用浏览器缓存,下次再用到的话直接从浏览器缓存中读取。
简单使用
Before
import OtherComponent from './OtherComponent';
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
After
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
React.lazy接收一个函数作为参数,该函数必须通过调用import来返回一个Promise来加载组件,返回的Promise对象的resolve方法接收组件默认导出的模块
如果只是这么使用运行会出现错误,根据提示需要引入Suspense组件
Suspense类似于一个错误捕获器,允许定义一个fallback指示符,fallback用来定义我们在等待加载时显示的一些内容。可以将Suspense组件放在动态加载组件上方的任何位置,如果动态组件未加载,则从该组件开始向上寻找,直到找到Suspense组件。可以使用单个Suspense组件包裹多个动态加载组件
完整示例
const OtherComponent = React.lazy(() => import(/* webpackChunkName:"OtherComponent" */'./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<Loading/>}>
<p>下面是一个动态加载的组件</p>
<OtherComponent />
</Suspense>
</div>
);
}
真正项目中应用会遇到的问题总结
改造之前的文件大小
因为我的项目是个多页面应用,我接下来会以desktop.js这个入口来演示,其中vendor.dll.js是一个第三方库分离的文件。
可以看到,当前这个js文件大小Gzip压缩之后为603.2kb,没有其他任何异步加载的chunk
使用react lazy异步加载改造
不要忘记修改webpack配置
output: {
chunkFilename: "static/js/[name].[chunkhash:8].chunk.js"
},
如果使用了babel转译,则需要安装babel-plugin-syntax-dynamic-import
插件来解析动态import语法,修改babel配置
{
"plugins": ["syntax-dynamic-import"]
}
看一下打包后的文件大小
可以看到,desktop.js文件已经缩小到了459.69kb,比之前缩小了150kb(因为我只做了部分组件的异步加载,所以文件大小变化不是太大)。下图的Subtile.chunk是我点击按钮之后进行加载的组件
提取异步chunk中的公共资源
为了对比清晰,我这张图只包含了MultiLayoutDialog.chunk(66.84kb)和MultiLayoutPollingDialog.chunk(47.55kb)两个模块
情况不太乐观,两个组件的node_modules中都分别包含antd/lib,rc-tree,rc-animate,这是因为每个chunk都是一个独立的可运行的模块,因此会加载自己的依赖,但是显然这是不合理的,应该将这些公共的依赖抽离出来,这个时候你需要用到CommonsChunkPlugin,配置async,用于从异步chunk中抽离公共的资源为一个单独的文件,当首个异步chunk被加载时会同步加载该公共资源文件
重新配置webpack
new webpack.optimize.CommonsChunkPlugin({
async: "common-in-lazy",
minChunks: ({ resource }) =>
resource && resource.indexOf("node_modules") >= 0 && resource.match(/\.js$/),
children: true,
})
// 在所有的async chunk中查找被其中至少2个chunk所共享的node_modules资源,将它们挪到common-in-lazy文件中,没有则新建。
再来看提取了公共资源之后的打包文件大小
MultiLayoutDialog.chunk(19.85kb)和MultiLayoutPollingDialog.chunk(26.21kb)两个chunk分别减少了46kb和20kb,(另外还有一些其他的chunk体积也减少了,在此没有截图),公共资源抽离为了common-in-lazy-desktop.chunk,该chunk中包含了之前重复加载的antd/lib,rc-tree,rc-animate等资源,改文件大小为50.09kb
拯救丢失的css
不要以为到此异步加载就大功告成了,如果你敢build之后部署,你会收到qa小姐姐亲切问候的,因为有些异步加载的组件样式丢失啦,导致页面显示出现问题。正在喝茶的你吓得立马坐正一波谷歌之后,决定将异步chunk的css样式与入口的css合并到一个css文件,只做js的异步chunk加载,修改webpack配置
new ExtractTextPlugin({
filename: cssFilename,
allChunks: true // 默认为false,仅从初始chunk中提取
}),
至此,整个异步加载已经全部完成,继续喝茶,总结一下:
1. react的lazy方法需要配合Suspense组件来使用,可以定义异步chunk加载未完成之前的UI显示
2.webpack配置的出口要添加chunkFilename: "static/js/[name].[chunkhash:8].chunk.js"
3.如果使用了babel,为避免babel将异步import转译,则需要安装额外的插件来识别动态import
4.打包的异步chunk会各自包含自己的依赖,这些依赖会存在重复加载,可以使用commonsChunksPlugin将异步chunk中的公共资源提取为一个独立的异步chunk
5.如果异步chunk的部分样式有丢失,将extractTextPlugin中chunks配置为all,则将所有chunk中的css样式抽离为一个文件,(但是这样做的话,css不会实现异步加载了,感觉不太好,如果大家有好的建议欢迎提出)
以上就是我要分享的内容,如果存在错误的理解或者更好的理解方法,欢迎提出指正。