基于Webpack 2的React组件懒加载从属于笔者的Web 前端入门与工程实践,更多前端思考参阅笔者的2016-我的前端之路:工具化与工程化。
Chunks是Webpack的基本概念之一,最直观的概念是在多入口配置中,诶个单独的入口会生成单独的Chunk。而在添加额外的插件配置之后,Webpack会输出譬如独立的CSS包体这样独立的块。Webpack内置有如三种类型的Chunk:
Entry Chunks:Entry Chunks是我们最常见的Chunks类型,包含了应用所需要的Webpack运行时与即刻加载的模块。
Normal Chunks:Normal Chunks并不会包含Webpack运行时,主要指代那些应用运行时动态加载的模块,Webpack会为我们创建类似于JSONP这样合适的加载器来进行动态加载。
Initial Chunks:Initial Chunks本质上还是Normal Chunks,不过其会在应用初始化时完成加载,往往这个类型的Chunks由CommonsChunkPlugin生成。
bundle-loader
bundle-loader是Webpack官方出品的Loader之一,bundle-loader可以用来加载异步代码块,基本的用法如下:
// 当请求某个Bundle时,Webpack会为我们自动加载
var waitForChunk = require("bundle-loader!./file.js");
//我们需要等待Chunk加载完成才能获取到文件详情
waitForChunk(function(file) {
// use file like is was required with
// var file = require("./file.js");
});
// wraps the require in a require.ensure block
我们同样可以自定义Chunk名:
require("bundle-loader?lazy&name=my-chunk!./file.js");
我们可以很方便地利用bundle-loader实现React Router中模块的懒加载,譬如如果我们的路由设置如下:
import HomePage from "./pages/HomePage";
import AdminPage from "./pages/admin/AdminPage";
import AdminPageSettings from "./pages/admin/AdminPageSettings";
export default function routes(fromServer) {
return (
)
}
其中AdminPage可能非常笨重,我们希望只有当用户真实请求到/admin
这个地址时才会加载相关组件,此时我们就可以在Webpack配置中添加bundle-loader的支持:
{
...
module: {
loaders: [{
// use `test` to split a single file
// or `include` to split a whole folder
test: /.*/,
include: [path.resolve(__dirname, 'pages/admin')],
loader: 'bundle?lazy&name=admin'
}]
}
...
}
该配置会自动帮我们从主文件中移除admin相关的组件代码,然后将其移动到1.admin.js
文件中,然后在React Router中,我们同样需要冲定义组件加载函数:
import HomePage from "./pages/HomePage";
import AdminPage from "./pages/admin/AdminPage";
import AdminPageSettings from "./pages/admin/AdminPageSettings";
const isReactComponent = (obj) => Boolean(obj && obj.prototype && Boolean(obj.prototype.isReactComponent));
const component = (component) => {
return isReactComponent(component)
? {component}
: {getComponent: (loc, cb)=> component(
comp=> cb(null, comp.default || comp))}
};
export default function routes(fromServer) {
return (
)
}
React 懒加载组件封装
Lazy Loading - React
有时候我们需要将某个厚重的组件设置为异步加载,这里我们将常见的懒加载操作封装为某个组件及其高阶组件接口,源代码参考LazilyLoad:
import React from 'react';
/**
* @function 支持异步加载的封装组件
*/
class LazilyLoad extends React.Component {
constructor() {
super(...arguments);
this.state = {
isLoaded: false,
};
}
componentWillMount() {
this.load(this.props);
}
componentDidMount() {
this._isMounted = true;
}
componentWillReceiveProps(next) {
if (next.modules === this.props.modules) return null;
this.load(next);
}
componentWillUnmount() {
this._isMounted = false;
}
load(props) {
this.setState({
isLoaded: false,
});
const {modules} = props;
const keys = Object.keys(modules);
Promise.all(keys.map((key) => modules[key]()))
.then((values) => (keys.reduce((agg, key, index) => {
agg[key] = values[index];
return agg;
}, {})))
.then((result) => {
if (!this._isMounted) return null;
this.setState({modules: result, isLoaded: true});
});
}
render() {
if (!this.state.isLoaded) return null;
return React.Children.only(this.props.children(this.state.modules));
}
}
LazilyLoad.propTypes = {
children: React.PropTypes.func.isRequired,
};
export const LazilyLoadFactory = (Component, modules) => {
return (props) => (
{(mods) => }
);
};
export const importLazy = (promise) => (
promise.then((result) => result.default)
);
export default LazilyLoad;
回调方式懒加载
这里我们使用类似于bundle-loader中的回调方式进行懒加载,不过将其封装为了组件形式。其中的importLazy
主要是为了兼容Babel/ES2015,其只是单纯的返回默认属性值,实例代码参考这里。
render(){
return ...
importLazy(System.import('../lazy/loaded_late.js'))
}}>
{
({LoadedLate}) => {
return
}
}
...
}
高阶组件方式懒加载
在入门介绍中我们讲过可以利用external属性来配置引入jQuery,而这里我们也可以使用高阶组件方式进行异步加载:
// @flow
import React, { Component, PropTypes } from 'react';
import { LazilyLoadFactory } from '../../../common/utils/load/lazily_load';
/**
* 组件LoadedJquery
*/
export default class LoadedJQuery extends Component {
/**
* @function 默认渲染函数
*/
render() {
return (
this.props.$(ref).css('background-color', 'red')}>
jQuery加载完毕
);
}
}
export default LazilyLoadFactory(
LoadedJQuery,
{
$: () => System.import('jquery'),
}
);
这里我们将加载完毕的jQuery作为组件的Props参数传入到组件中使用,同样我们也可以使用这种方式加载我们自定义的函数或者组件。上述两种的效果如下所示: