bundle-loader 实现 react 各类按需加载
react 做为一个优秀的单页 webapp 框架,已经被广泛用于多种场景。随着当下前端业务体量的增大,有时候单页带来的一些问题也需要解决。
bundle 全量包在页面多、体量大业务下就显出它的不足了:每个“页面”中都是加载全都的js,其实很多其它页面的功能是不需要的,我们希望在用到它的时候再去加载。
翻一翻官方的 loader、plugin 列表,发现以下几个 loader 应该可以解决这个问题:
- bundle: Wraps request in a require.ensure block (callback)
- promise: Wraps request in a require.ensure block (promise)
- async-module: Same as bundle, but provides a way to handle script loading errors. Wraps request in a require.ensure block (callback, errback)
以第一个为例,逐步介绍一下它的用法。
基础用法
文档中已经有详细说明:
var waitForChunk = require("bundle-loader!./file.js");
// To wait until the chunk is available (and get the exports)
// you need to async wait for it.
waitForChunk(function(file) {
// use file like it was required with
// var file = require("./file.js");
});
就是包了一个 function,通过其参数获得真正的加载模块。这里不再多述。
react 中使用
react 中使用它稍有点抽象,我在之前两篇博文简单介绍了“高阶函数”,在这里我们就可以用高阶函数的思想实现一个 react 的按需加载策略。
首先来调整一下思路,先想一下以下的概念:
- react component 也是一个函数(这样仅有助于理解);
- react jsx 标签也可以是一个动态变量;
然后再去看一下官方的这篇讲 react 高阶函数的文章:Higher-Order Components。没看明白不要紧,建议看完我这一篇再回去看。
实现思路
bundle-loader
加载来的内容不能直接使用(看“基础用法”一章),需要我们封装一个方法这是必需的,借此可以写一个统一的 react-lazyloader 组件作为此类组件的入口,这样来实现按需加载。
demo
demo 设定三个文件:
- index.jsx:主页面
- common/lazyloader.jsx:加载器
- components/lazyComponent.jsx:需要懒加载的组件
lazyComponent.jsx
import React, { Component } from 'react';
class LazyComponent extends Component {
render() {
return (
<div>
this is a lazy Component!!!;
<br/>
name: {this.props.name} ===== tips: {this.props.tips}
</div>
);
}
}
export default LazyComponent;
很普通的一个 react 组件,不用在意。
index.jsx
import React, { Component } from 'react';
import LazyComponent from 'bundle-loader?lazy&name=lazy.[name]!./components/lazyComponent.jsx';
import LazyLoader from './common/lazyloader';
// debugger; 打开这个可以看“按需”js加载过程
class Index extends Component {
render() {
return (
<div>
this is index page!!!
<LazyLoader component={LazyComponent} name={'lazyname'} tips={'lazytips'}/>
</div>
);
}
}
export default Index;
index.jsx
中,我们引入了需要加载的组件 LazyComponent
和相应的加载器 LazyLoader
,通过它来对 LazyComponent
进行按需加载。
lazyloader.jsx
import React, { Component } from 'react';
class LazyLoader extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
props: null,
};
this._isMounted = false; // 这个需要考虑
}
componentWillMount() {
this._load();
}
componentDidMount() {
this._isMounted = true;
}
componentWillReceiveProps(next) {
if (next.component === this.props.component) {
return null;
}
this._load();
}
componentWillUnmount() {
this._isMounted = false;
}
_load() {
this.props.component( com => {
this.setState({
component: com.default || com, // 兼容 es6 => commonjs
});
});
}
render() {
const LazyComponent = this.state.component;
const props = Object.assign({}, this.props); // clone props
delete props.component; // 去掉 component
return LazyComponent ? (
<LazyComponent {...props}/>
) : null;
}
}
LazyLoader.propTypes = {
component: ReactComponent.isRequire, // 要加载的组件
...props: {any} // 组件的属性 props
}
export default LazyLoader;
加载器核心功能实际也不复杂,就是将接收到的 component
进行动态的渲染就可以了,开始要调整好那二点思路,看这里就很简单了。
执行结果
最后我们来验证一下结果是否可行。
- js 的 loader 配置:
{
test: /\.(js|jsx)$/,
loader: 'babel',
exclude: /node_modules/,
query: {
plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-decorators-legacy', 'transform-class-properties'],
presets: ['es2015', 'react', 'stage-2'],
},
},
- 先看页面结果:
没问题。重要的是 js 的加载情况:
- index.jsx debugger 执行前:
- index.jsx debugger 执行后:
注意:笔者环境下
index
也是懒加载的,所以有1.index.app.js
这个东西,按文中结果是没有的。
是不是和想像的一样~~~, 在我们需要这个组件的时候它跑来(*.lazy_lazyComponent.js)。
下一篇中,再介绍一下 bundle-loader 在 webpack.config.js 中的配置使用,和在
react-router
中的结构化实现,这个实用性更高哦。