基於Webpack 2的React組件懶載入

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

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

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) => ( <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,而這裡我們也可以使用高階組件方式進行非同步載入:

// @flowimport 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參數傳入到組件中使用,同樣我們也可以使用這種方式載入我們自定義的函數或者組件。上述兩種的效果如下所示:

推薦閱讀:

非計算機專業,半路出家,以前做 UI ,現在想改前端,學習 JS 過程中遇到瓶頸了,如何突破?
11行代碼帶你搞懂 Generator 函數
PHP 是做前台還是後台?前端和前台的區別?
雙向綁定的簡單實現——基於「臟檢測」

TAG:webpack | React | 前端开发 |