代码分割,动态导入的场景
简单来说,就是例如我们的页面中,有一个组件内部实现逻辑非常庞大,引入了
许多其他的包,导致该页面整体体积过大,影响首屏渲染速度,此时我们期望优先加载
其他内容,最后再去加载这块组件内容,避免首屏渲染时间过长。也就是从chunks再拆分出
部分逻辑,单独生成一个script文件,在我们需要的时机进行请求该模块的script文件,再将
它显示出来,这样就是代码分割,动态引入的一个经典场景。
动态导入的实现原理
动态导入,本质上是来自于ECMA关于异步import语法的提案,该语法是让import支持异步引入。
例如import('./xxxfile').then(res=>{})
,使得import语法promise异步化,res中就可以拿到
该模块的语法资源,再由开发者对其进行处理,例如,在webpack的实现中,引入一个react组件的res是这样的
一个JS对象:
其中default便是该模块默认到处的内容,也就是我们默认导出的组件了。
实践例子
1.模拟一下场景,我们有一个需要被拆分的 react 组件 DynamicTest.js
import React from 'react';
import styles from './DynamicTest.less';
function DynamicTest() {
// 实际案例中,这里的实现逻辑可能非常复杂,我们想把它单独拎出来
return <div className={styles.root}>动态加载组件内容</div>;
}
export default DynamicTest;
以及其组件样式文件 ./DynamicTest.less
,
现在我们期望把这个文件和其样式文件单独从打包chunk中拆分出来。
原来的webpack输出:
chunk.js
chunk.css
期望的webpack输出:
chunk.js
chunk.css
DynamicTest.js
DynamicTest.chunk.css
2.对动态导入的逻辑进行封装
在对DynamicTest
进行动态导入的时候,我们期望能实现以下功能 createLoadable:
- 资源正在加载的时候,显示Loading占位符组件
- 资源加载错误的时候,显示Error占位符组件
- 资源加载完成的时候,显示DynamicTest组件
- 允许设置一个delay,来延迟加载的时机
也就是以下的调用例子,createLoadable传入以上需求的配置,返回一个可动态导入的react组件:
const DynamicTest = createLoadable({
// loader(func),返回动态导入的文件路径
// 其中 /* webpackChunkName: "DynamicTest" */ 是webpack规定的,来命名打包后文件的名字
loader: () => import(/* webpackChunkName: "DynamicTest" */ './DynamicTest'),
// loading(reactComponent) react组件,props是loading(正在加载)以及err(发生错误)
// 我们可以根据其props进行样式定制化
loading: ({loading,err})=> loading ? 'loading' : err ? 'error': null,
// delay(ms) 延迟加载的时间
delay: 5000,
});
在我们的页面中,调用DynamicTest组件
export default () => {
return (
<Wrapper>
<DynamicTest/>
</Wrapper>
);
};
- createLoadable的实现逻辑
import React, { useState, useEffect } from 'react';
// loader组件,控制动态引入的状态
function Loader({ loader, loading, delay, loadedComponentProps }) {
// import进来的模块内容
const [loaded, setLoaded] = useState(null);
// err引入发生的错误,isLoading是否正在加载中
const [err, setErr] = useState(null);
const [isLoading, setLoading] = useState(false);
const load = () => {
loader()
.then(_loaded => {
// log =>{__esModule: true, Symbol(Symbol.toStringTag): "Module", default: ƒ}
setLoaded(_loaded);
})
.catch(_err => {
setErr(_err);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
setLoading(true);
let h = null;
if (delay) {
h = setTimeout(load, delay);
} else {
load();
}
return () => {
clearTimeout(h);
};
}, []);
// 加载中或者错误
// 返回占位符组件
if (isLoading || err) {
return React.createElement(loading, { isLoading, err });
}
// 加载成功
// 返回加载成功后的组件
if (loaded) {
const loadedComponent =
loaded && loaded.__esModule ? loaded.default : loaded;
return React.createElement(loadedComponent, loadedComponentProps);
}
return null;
}
// 默认的占位符组件
const DefaultLoading = ({ loading, error }) => {
// 组件正在异步加载的时候
if (loading) return <div>Loading</div>;
// 组件加载发生了错误的时候
if (error) return <div>Unknown error occurred</div>;
return null;
};
/**
* @method createLoadable
* @desc 通过import动态导入的语法,返回一个React组件,在合适的时机展示loading与err以及异步加载组件的内容
* @param {loadr|fn} () => import('./DynamicTest') 动态引入
* @param {loading|component} 占位符组件,会被注入isLoading、err的props
* @param {delay|ms} 延迟加载的时间
* */
const createLoadable = ({ loader, loading = DefaultLoading, delay = 0 }) => {
return props =>
React.createElement(Loader, {
loader,
loading,
delay,
loadedComponentProps: props,
});
};
export default createLoadable;
createLoadable的实现简而言之,就是创建了一个react组件loader,该组件帮助我们来管理import动态导入时的状态,
加载中、加载失败、以及加载完成,再去选择展示的组件,以及加上了延迟加载的功能需要。
预览
-
正在加载的时候
-
加载完成,展示组件内容
-
network中单独出来的chunk
其他:如何修改打包后的分片文件名
1.使用webpack,修改
output: {
chunkFilename: '[name].bundle.js',
}
2.使用webpack-chain
config.output.chunkFilename(’[name].bundle.js’);
output中的chunkFilename其实就是entry中没有的其他chunk的命名规则。
总结
以上便是在react中如何使用webpack的动态导入特性的一个实践例子了,其实就是利用了webpack对import异步化语法的支持,
文件拆分的逻辑由webpack完成,我们通过编写一个组件来管理动态引入可能出现的几种状态,来显示我们期望的内容,甚至还可以
在导入失败的时候重新加载。