本文讲述怎么实现动态加载组件,并借此阐述适配器模式。javascript
import Center from 'page/center'; import Data from 'page/data'; function App(){ return ( <Router> <Switch> <Route exact path="/" render={() => (<Redirect to="/center" />)} /> <Route path="/data" component={Data} /> <Route path="/center" component={Center} /> <Route render={() => <h1 style={{ textAlign: 'center', marginTop: '160px', color:'rgba(255, 255, 255, 0.7)' }}>页面不见了</h1>} /> </Switch> </Router> ); }
以上是最多见的React router
。在简单的单页应用中,这样写是ok的。由于打包后的单一js文件bundle.js
也不过200k左右,gzip
以后,对加载性能并无太大的影响。
可是,当产品经历屡次迭代后,追加的页面致使bundle.js
的体积不断变大。这时候,优化就变得颇有必要。java
优化使用到的一个重要理念就是——按需加载。
能够结合例子进行理解为:只加载当前页面须要用到的组件。 react
好比当前访问的是/center
页,那么只须要加载Center
组件便可。不须要加载Data
组件。 webpack
业界目前实现的方案有如下几种:git
getComponent
方法(router4已不支持)而这些方案共通的点,就是利用webpack的code splitting
功能(webpack1使用require.ensure
,webpack2/webpack3使用import
),将代码进行分割。github
接下来,将介绍如何用自定义高阶组件实现按需加载。web
webpack将import()
看作一个分割点并将其请求的module打包为一个独立的chunk。import()
以模块名称做为参数名而且返回一个 Promise对象。
由于import()
返回的是Promise对象,因此不能直接给<Router/>
使用。segmentfault
适配器模式(Adapter):将一个类的接口转换成客户但愿的另一个接口。Adapter模式使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。
当前场景,须要解决的是,使用import()
异步加载组件后,如何将加载的组件交给React进行更新。
方法也很容易,就是利用state
。当异步加载好组件后,调用setState
方法,就能够通知到。
那么,依照这个思路,新建个高阶组件,运用适配器模式
,来对import()
进行封装。react-router
import React from 'react'; export const asyncComponent = loadComponent => ( class AsyncComponent extends React.Component { constructor(...args){ super(...args); this.state = { Component: null, }; this.hasLoadedComponent = this.hasLoadedComponent.bind(this); } componentWillMount() { if(this.hasLoadedComponent()){ return; } loadComponent() .then(module => module.default ? module.default : module) .then(Component => { this.setState({ Component }); }) .catch(error => { /*eslint-disable*/ console.error('cannot load Component in <AsyncComponent>'); /*eslint-enable*/ throw error; }) } hasLoadedComponent() { return this.state.Component !== null; } render(){ const { Component } = this.state; return (Component) ? <Component {...this.props} /> : null; } } );
// 使用方式 const Center = asyncComponent(()=>import(/* webpackChunkName: 'pageCenter' */'page/center'));
如例子所示,新建一个asyncComponent
方法,用于接收import()
返回的Promise对象。
当componentWillMount
时(服务端渲染也有该生命周期方法),执行import()
,并将异步加载的组件,set
入state
,触发组件从新渲染。异步
this.state = { Component: null, };
这里的null
,主要用于判断异步组件是否已经加载。
module.default ? module.default : module
这里是为了兼容具名
和default
两种export
写法。
return (Component) ? <Component {...this.props} /> : null;
这里的null
,其实能够用<LoadingComponent />
代替。做用是:当异步组件还没加载好时,起到占位的做用。 this.props
是经过AsyncComponent
组件透传给异步组件的。
output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }
在输出项中,增长chunkFilename
便可。
自定义高阶组件的好处,是能够按最少的改动,来优化已有的旧项目。
像上面的例子,只须要改变import
组件的方式便可。花最少的代价,就能够获得页面性能的提高。
其实,react-loadable
也是按这种思路去实现的,只不过增长了不少附属的功能点而已。
喜欢我文章的朋友,能够经过如下方式关注我: