基於Webpack 2的React組件懶加載

基於Webpack 2的React組件懶加載從屬於筆者的Web 前端入門與工程實踐,更多前端思考參閱筆者的2016-個人前端之路:工具化與工程化css

Chunks是Webpack的基本概念之一,最直觀的概念是在多入口配置中,誒個單獨的入口會生成單獨的Chunk。而在添加額外的插件配置以後,Webpack會輸出譬如獨立的CSS包體這樣獨立的塊。Webpack內置有如三種類型的Chunk:html

  • Entry Chunks:Entry Chunks是咱們最多見的Chunks類型,包含了應用所須要的Webpack運行時與即刻加載的模塊。前端

  • Normal Chunks:Normal Chunks並不會包含Webpack運行時,主要指代那些應用運行時動態加載的模塊,Webpack會爲咱們建立相似於JSONP這樣合適的加載器來進行動態加載。react

  • Initial Chunks:Initial Chunks本質上仍是Normal Chunks,不過其會在應用初始化時完成加載,每每這個類型的Chunks由CommonsChunkPlugin生成。jquery

bundle-loader

bundle-loader是Webpack官方出品的Loader之一,bundle-loader能夠用來加載異步代碼塊,基本的用法以下:webpack

// 當請求某個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名:git

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

咱們能夠很方便地利用bundle-loader實現React Router中模塊的懶加載,譬如若是咱們的路由設置以下:github

import HomePage from "./pages/HomePage";
import AdminPage from "./pages/admin/AdminPage";
import AdminPageSettings from "./pages/admin/AdminPageSettings";
export default function routes(fromServer) {
  return (
    <Router history={browserHistory}>
      <Route path="/" component={HomePage}/>
      <Route path="/admin" component={AdminPage}/>
      <Route path="/admin/settings" component={AdminSettingsPage}/>
    <Router/>
  )
}

其中AdminPage可能很是笨重,咱們但願只有當用戶真實請求到/admin這個地址時纔會加載相關組件,此時咱們就能夠在Webpack配置中添加bundle-loader的支持:web

{
...
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中,咱們一樣須要衝定義組件加載函數:promise

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 (
    <Router history={browserHistory}>
      <Route path="/" {...component(HomePage)}/>
      <Route path="/admin" {...component(AdminPage)}/>
      <Route path="/admin/settings"      
                  {...component(AdminSettingsPage)}/>
    <Router/>
  )
}

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) => (
    <LazilyLoad modules={modules}>
      {(mods) => <Component {...mods} {...props} />}
    </LazilyLoad>
  );
};

export const importLazy = (promise) => (
  promise.then((result) => result.default)
);

export default LazilyLoad;

回調方式懶加載

這裏咱們使用相似於bundle-loader中的回調方式進行懶加載,不過將其封裝爲了組件形式。其中的importLazy主要是爲了兼容Babel/ES2015,其只是單純的返回默認屬性值,實例代碼參考這裏

render(){
    return ...
        <LazilyLoad modules={{
          LoadedLate: () => importLazy(System.import('../lazy/loaded_late.js'))
        }}>
          {
            ({LoadedLate}) => {
              return <LoadedLate />
            }
          }
        </LazilyLoad>
   ...
}

高階組件方式懶加載

在入門介紹中咱們講過能夠利用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 (
      <div
        ref={(ref) => this.props.$(ref).css('background-color', 'red')}>
        jQuery加載完畢
      </div>
    );

  }

}

export default LazilyLoadFactory(
  LoadedJQuery,
  {
    $: () => System.import('jquery'),
  }
);

這裏咱們將加載完畢的jQuery做爲組件的Props參數傳入到組件中使用,一樣咱們也可使用這種方式加載咱們自定義的函數或者組件。上述兩種的效果以下所示:

相關文章
相關標籤/搜索