组件的异步加载,通常会有3种处理方式:
- System.import();
- require.ensure();
- import();
System.import目前已废除,不多说。本文着重介绍require.ensure
和import
的方式来实线组件的异步加载(懒加载Lazyload)。
先说require.ensure
require.ensure
配合react-router,可以轻松实现代码切割。
react-router 2+、3+可以,4中已不支持getComponent的prop,本文用到该方法的示例中,用的是react-router@3.0.0。
另外,测试了一下,react/react-dom(16.2.0)与react-router 3兼容性不好,建议用正确版本搭配使用。
首先在webpack的output中配置文件名约束,否则生成的代码会是0.js/1.js,不利于调试阅读:
1 2 3 4 5 6
| output: { chunkFilename: 'js/[name].[chunkhash:10].js', }
|
一个简单的demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const Main = (location, callback) => { require.ensure([], require => { callback(null, require('./Main/').default) },'main'); }; const Toast = (location, callback) => { require.ensure([], require => { callback(null, require('./Toast/').default) },'toast'); }; const Page1 = (location, callback) => { require.ensure([], require => { callback(null, require(`./Page1/`).default); }, 'page1'); };
render( <Router history={browserHistory}> <Route path="/" getComponent={Main} /> <Route path="/toast" getComponent={Toast} /> <Route path="/page1" getComponent={Page1} /> </Router>, document.querySelector('#react') );
|
一切正常,代码也做好了拆分:


等一下,你是前端吗,这代码写的太矬了,公用部分都没有抽出来。
(擦汗…)我改一下,提高代码的复用性
,写个函数先:
1 2 3 4 5
| function requireEnsure(location, callback, name) { require.ensure([], require => { callback(null, require(`./${name}/`).default); }, name); }
|
保存。呃,控制台给给出了警告:

英文虽然不好但也能看出大意:require函数没有使用静态提取的方式管理依赖
。
看来是忽略了一点,require、import引入模块的时候,必须要用字符串,否则webpack在编译阶段是无法识别需要引用哪个模块的。
警告而已,不管他,能实现功能就ok。试一个组件:
1 2 3
| const Page1 = (location, callback) => { requireEnsure(...arguments, 'Page1'); };
|
报错信息如下:

“webpack不是有context吗?”嗯,那我在webpack导出模块中加入context试一下:
1 2 3 4
| module.exports = { context: process.pwd() };
|
嗯,IDE控制台的警告消失了,预览一下页面看是不是成功了?
WTF,页面空白,打开调试工具,报错了:


感觉思路应该没问题,可能是我姿势不对,有兴趣的同学可以深挖一下,成功了别忘了同步给大家。
再来介绍如何用import解决异步加载组件的问题
这次先抽公共代码——虽然上面的公共代码并不能抽离:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React, { Component } from 'react';
const AsyncComponent = loadComponent => ( class AsyncComp extends Component { state = { Component: null, }; componentWillMount() { if (this.hasLoadedComponent()) { return; } loadComponent() .then(module => module.default) .then((Component) => { this.setState({Component}); }) .catch((err) => { console.error(`Cannot load component in <AsyncComp />`); throw err; }); } hasLoadedComponent() { return this.state.Component !== null; } render() { const {Component} = this.state; return (Component) ? <Component {...this.props} /> : null; } } );
export default AsyncComponent;
|
在使用的时候,我们只需要这样:
1 2 3 4 5 6 7 8 9 10 11 12
| import asyncComponent from './util/'; const Main = asyncComponent(() => import( "./Main/")); const Page1 = asyncComponent(() => import( "./Page1/"));
<Router> <Switch> <Route exact path="/" component={Main} /> <Route path="/page1" component={Page1} /> </Switch> </Router>
|
跟require.ensuer一样,我们得到了想要的:

完了?就酱?这些我都会啊!

那就再说个可能没怎么用过的。
在一个功能复杂、逻辑较多的项目中,仅仅通过结合Router用webpack做Code splitting远远不够,业务代码可能还是会很大。
那,其他组件怎么做异步加载?
Talk is cheap, show me the code!
我们来个简单的栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| const Load = asyncComponent(() => import( "./LoadComponent/"));
<Route path="/load" component={Load} />
import React, { Component } from 'react'; import asyncComponent from '../util/';
const Page2 = asyncComponent(() => import( "../Page2/"));
export default class LoadComponent extends Component { static defaultProps = { text: 'default text', btnText: 'click here', btnHandle() { console.log('default button handle.'); }, }; constructor() { super(); this.loadComponent = this.loadComponent.bind(this); } state = { newComp: null, }; loadComponent() { this.setState({ newComp: Page2, }); } render() { const { text, btnText, } = this.props; return ( <div className="toast-container"> <span>点击下面的按钮,动态加载一个组件。</span> { this.state.newComp && <Page2 /> } <p> <button onClick={this.loadComponent}>加载组件</button> </p> </div> ) } };
|
Page2.jsx
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component } from 'react';
const Page2 = () => ( [ <h2 key={0}>Page2 content.</h2>, <button key={1} onClick={() => { console.log('clicked in page2'); }}>page2中的点击事件</button> ] );
export default Page2;
|
看一下效果:

在业务中动态加载了组件Page2:

Page2中的事件也能正常执行:

好了,就酱,谢谢你的时间。