基于Webpack 2的React组件懒加载

开发 开发工具
Chunks是Webpack的基本概念之一,最直观的概念是在多入口配置中,诶个单独的入口会生成单独的Chunk。而在添加额外的插件配置之后,Webpack会输出譬如独立的CSS包体这样独立的块。

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可以用来加载异步代码块,基本的用法如下:

  1. // 当请求某个Bundle时,Webpack会为我们自动加载 
  2. var waitForChunk = require("bundle-loader!./file.js"); 
  3.  
  4. //我们需要等待Chunk加载完成才能获取到文件详情 
  5. waitForChunk(function(file) { 
  6.     // use file like is was required with 
  7.     // var file = require("./file.js"); 
  8. }); 
  9. // wraps the require in a require.ensure block 

我们同样可以自定义Chunk名:

  1. require("bundle-loader?lazy&name=my-chunk!./file.js"); 

我们可以很方便地利用bundle-loader实现React Router中模块的懒加载,譬如如果我们的路由设置如下:

  1. import HomePage from "./pages/HomePage"
  2. import AdminPage from "./pages/admin/AdminPage"
  3. import AdminPageSettings from "./pages/admin/AdminPageSettings"
  4. export default function routes(fromServer) { 
  5.   return ( 
  6.     <Router history={browserHistory}> 
  7.       <Route path="/" component={HomePage}/> 
  8.       <Route path="/admin" component={AdminPage}/> 
  9.       <Route path="/admin/settings" component={AdminSettingsPage}/> 
  10.     <Router/> 
  11.   ) 

其中AdminPage可能非常笨重,我们希望只有当用户真实请求到/admin这个地址时才会加载相关组件,此时我们就可以在Webpack配置中添加bundle-loader的支持:

  1. ... 
  2. module: { 
  3.   loaders: [{ 
  4.     // use `test` to split a single file 
  5.     // or `include` to split a whole folder 
  6.     test: /.*/, 
  7.     include: [path.resolve(__dirname, 'pages/admin')], 
  8.     loader: 'bundle?lazy&name=admin' 
  9.    }] 
  10.    
  11. ... 

该配置会自动帮我们从主文件中移除admin相关的组件代码,然后将其移动到1.admin.js文件中,然后在React Router中,我们同样需要冲定义组件加载函数:

  1. import HomePage from "./pages/HomePage"
  2. import AdminPage from "./pages/admin/AdminPage"
  3. import AdminPageSettings from "./pages/admin/AdminPageSettings"
  4. const isReactComponent = (obj) => Boolean(obj && obj.prototype && Boolean(obj.prototype.isReactComponent)); 
  5.  
  6. const component = (component) => { 
  7.   return isReactComponent(component) 
  8.     ? {component} 
  9.     : {getComponent: (loc, cb)=> component( 
  10.          comp=> cb(null, comp.default || comp))} 
  11. }; 
  12. export default function routes(fromServer) { 
  13.   return ( 
  14.     <Router history={browserHistory}> 
  15.       <Route path="/" {...component(HomePage)}/> 
  16.       <Route path="/admin" {...component(AdminPage)}/> 
  17.       <Route path="/admin/settings"       
  18.                   {...component(AdminSettingsPage)}/> 
  19.     <Router/> 
  20.   ) 

React 懒加载组件封装

有时候我们需要将某个厚重的组件设置为异步加载,这里我们将常见的懒加载操作封装为某个组件及其高阶组件接口,源代码参考LazilyLoad:

  1. import React from 'react'
  2.  
  3. /** 
  4.  * @function 支持异步加载的封装组件 
  5.  */ 
  6. class LazilyLoad extends React.Component { 
  7.  
  8.   constructor() { 
  9.     super(...arguments); 
  10.     this.state = { 
  11.       isLoaded: false
  12.     }; 
  13.   } 
  14.  
  15.   componentWillMount() { 
  16.     this.load(this.props); 
  17.   } 
  18.  
  19.   componentDidMount() { 
  20.     this._isMounted = true
  21.   } 
  22.  
  23.   componentWillReceiveProps(next) { 
  24.     if (next.modules === this.props.modules) return null
  25.     this.load(next); 
  26.   } 
  27.  
  28.   componentWillUnmount() { 
  29.     this._isMounted = false
  30.   } 
  31.  
  32.   load(props) { 
  33.     this.setState({ 
  34.       isLoaded: false
  35.     }); 
  36.  
  37.     const {modules} = props; 
  38.     const keys = Object.keys(modules); 
  39.  
  40.     Promise.all(keys.map((key) => modules[key]())) 
  41.       .then((values) => (keys.reduce((agg, keyindex) => { 
  42.         agg[key] = values[index]; 
  43.         return agg; 
  44.       }, {}))) 
  45.       .then((result) => { 
  46.         if (!this._isMounted) return null
  47.         this.setState({modules: result, isLoaded: true}); 
  48.       }); 
  49.   } 
  50.  
  51.   render() { 
  52.     if (!this.state.isLoaded) return null
  53.     return React.Children.only(this.props.children(this.state.modules)); 
  54.   } 
  55.  
  56. LazilyLoad.propTypes = { 
  57.   children: React.PropTypes.func.isRequired, 
  58. }; 
  59.  
  60. export const LazilyLoadFactory = (Component, modules) => { 
  61.   return (props) => ( 
  62.     <LazilyLoad modules={modules}> 
  63.       {(mods) => <Component {...mods} {...props} />} 
  64.     </LazilyLoad> 
  65.   ); 
  66. }; 
  67.  
  68. export const importLazy = (promise) => ( 
  69.   promise.then((result) => result.default
  70. ); 
  71.  
  72. export default LazilyLoad; 

回调方式懒加载

这里我们使用类似于bundle-loader中的回调方式进行懒加载,不过将其封装为了组件形式。其中的importLazy主要是为了兼容Babel/ES2015,其只是单纯的返回默认属性值,实例代码参考这里。

  1. render(){ 
  2.     return ... 
  3.         <LazilyLoad modules={{ 
  4.           LoadedLate: () => importLazy(System.import('../lazy/loaded_late.js')) 
  5.         }}> 
  6.           { 
  7.             ({LoadedLate}) => { 
  8.               return <LoadedLate /> 
  9.             } 
  10.           } 
  11.         </LazilyLoad> 
  12.    ... 

高阶组件方式懒加载

在入门介绍中我们讲过可以利用external属性来配置引入jQuery,而这里我们也可以使用高阶组件方式进行异步加载:

  1. // @flow 
  2. import React, { Component, PropTypes } from 'react'
  3. import { LazilyLoadFactory } from '../../../common/utils/load/lazily_load'
  4.  
  5. /** 
  6.  * 组件LoadedJquery 
  7.  */ 
  8. export default class LoadedJQuery extends Component { 
  9.  
  10.   /** 
  11.    * @function 默认渲染函数 
  12.    */ 
  13.   render() { 
  14.  
  15.     return ( 
  16.       <div 
  17.         ref={(ref) => this.props.$(ref).css('background-color''red')}> 
  18.         jQuery加载完毕 
  19.       </div> 
  20.     ); 
  21.  
  22.   } 
  23.  
  24.  
  25. export default LazilyLoadFactory( 
  26.   LoadedJQuery, 
  27.   { 
  28.     $: () => System.import('jquery'), 
  29.   } 
  30. ); 

这里我们将加载完毕的jQuery作为组件的Props参数传入到组件中使用,同样我们也可以使用这种方式加载我们自定义的函数或者组件。上述两种的效果如下所示:

【本文是51CTO专栏作者“张梓雄 ”的原创文章,如需转载请通过51CTO与作者联系】

戳这里,看该作者更多好文

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2024-03-20 09:31:00

图片懒加载性能优化React

2017-07-06 20:27:38

React.jsHtml5Javascript

2015-07-03 10:41:47

ReactWebpack

2015-10-08 10:58:51

图片懒加载

2021-08-16 12:32:37

HashMap八股文面试

2021-11-07 20:43:14

React

2022-06-07 08:18:49

懒加载Web前端

2021-02-21 11:40:25

技术优化实践

2020-12-03 10:40:23

webpack加载原理前端

2011-01-17 19:35:04

javascriptjqueryweb

2020-11-18 09:30:29

图片懒加载前端浏览器

2023-03-22 23:23:25

React加载动画库

2022-07-10 20:45:47

React加载动画库

2017-02-28 21:57:05

React组件

2019-07-22 10:42:11

React组件前端

2019-07-20 23:30:48

开发技能代码

2018-08-02 14:08:47

小程序javascriptlazyload

2019-09-09 09:05:59

图片框架懒加载

2021-03-03 08:32:09

开源子节点组件

2017-07-11 15:50:11

前端webpack2优化
点赞
收藏

51CTO技术栈公众号