Open Lemonreds opened 4 years ago
简单来说,就是例如我们的页面中,有一个组件内部实现逻辑非常庞大,引入了 许多其他的包,导致该页面整体体积过大,影响首屏渲染速度,此时我们期望优先加载 其他内容,最后再去加载这块组件内容,避免首屏渲染时间过长。也就是从chunks再拆分出 部分逻辑,单独生成一个script文件,在我们需要的时机进行请求该模块的script文件,再将 它显示出来,这样就是代码分割,动态引入的一个经典场景。
动态导入,本质上是来自于ECMA关于异步import语法的提案,该语法是让import支持异步引入。 例如import('./xxxfile').then(res=>{}),使得import语法promise异步化,res中就可以拿到 该模块的语法资源,再由开发者对其进行处理,例如,在webpack的实现中,引入一个react组件的res是这样的 一个JS对象:
import('./xxxfile').then(res=>{})
其中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中拆分出来。
./DynamicTest.less
原来的webpack输出:
chunk.js chunk.css
期望的webpack输出:
chunk.js chunk.css DynamicTest.js DynamicTest.chunk.css
2.对动态导入的逻辑进行封装
在对DynamicTest进行动态导入的时候,我们期望能实现以下功能 createLoadable:
DynamicTest
也就是以下的调用例子,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> ); };
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动态导入时的状态, 加载中、加载失败、以及加载完成,再去选择展示的组件,以及加上了延迟加载的功能需要。
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完成,我们通过编写一个组件来管理动态引入可能出现的几种状态,来显示我们期望的内容,甚至还可以 在导入失败的时候重新加载。
webpack关于动态导入的的文档: https://webpack.js.org/guides/code-splitting/#dynamic-imports
以上需求的一个更好的实现,UMIJS中动态导入的功能也基于此项目 https://github.com/jamiebuilds/react-loadable
https://github.com/Lemonreds/react-components/tree/master/src/pages/comp/examples/createLoadable 源码例子
代码分割,动态导入的场景
简单来说,就是例如我们的页面中,有一个组件内部实现逻辑非常庞大,引入了 许多其他的包,导致该页面整体体积过大,影响首屏渲染速度,此时我们期望优先加载 其他内容,最后再去加载这块组件内容,避免首屏渲染时间过长。也就是从chunks再拆分出 部分逻辑,单独生成一个script文件,在我们需要的时机进行请求该模块的script文件,再将 它显示出来,这样就是代码分割,动态引入的一个经典场景。
动态导入的实现原理
动态导入,本质上是来自于ECMA关于异步import语法的提案,该语法是让import支持异步引入。 例如
import('./xxxfile').then(res=>{})
,使得import语法promise异步化,res中就可以拿到 该模块的语法资源,再由开发者对其进行处理,例如,在webpack的实现中,引入一个react组件的res是这样的 一个JS对象:其中default便是该模块默认到处的内容,也就是我们默认导出的组件了。
实践例子
1.模拟一下场景,我们有一个需要被拆分的 react 组件 DynamicTest.js
以及其组件样式文件
./DynamicTest.less
, 现在我们期望把这个文件和其样式文件单独从打包chunk中拆分出来。原来的webpack输出:
期望的webpack输出:
2.对动态导入的逻辑进行封装
在对
DynamicTest
进行动态导入的时候,我们期望能实现以下功能 createLoadable:也就是以下的调用例子,createLoadable传入以上需求的配置,返回一个可动态导入的react组件:
在我们的页面中,调用DynamicTest组件
createLoadable的实现简而言之,就是创建了一个react组件loader,该组件帮助我们来管理import动态导入时的状态, 加载中、加载失败、以及加载完成,再去选择展示的组件,以及加上了延迟加载的功能需要。
预览
其他:如何修改打包后的分片文件名
1.使用webpack,修改
2.使用webpack-chain
config.output.chunkFilename('[name].bundle.js');
output中的chunkFilename其实就是entry中没有的其他chunk的命名规则。
总结
以上便是在react中如何使用webpack的动态导入特性的一个实践例子了,其实就是利用了webpack对import异步化语法的支持, 文件拆分的逻辑由webpack完成,我们通过编写一个组件来管理动态引入可能出现的几种状态,来显示我们期望的内容,甚至还可以 在导入失败的时候重新加载。
参考
webpack关于动态导入的的文档: https://webpack.js.org/guides/code-splitting/#dynamic-imports
以上需求的一个更好的实现,UMIJS中动态导入的功能也基于此项目 https://github.com/jamiebuilds/react-loadable