基于Webpack 2的React組件懶加載
Chunks是Webpack的基本概念之一,最直觀的概念是在多入口配置中,誒個單獨的入口會生成單獨的Chunk。而在添加額外的插件配置之后,Webpack會輸出譬如獨立的CSS包體這樣獨立的塊。Webpack內置有如三種類型的Chunk:
- Entry Chunks:Entry Chunks是我們最常見的Chunks類型,包含了應用所需要的Webpack運行時與即刻加載的模塊。
- Normal Chunks:Normal Chunks并不會包含Webpack運行時,主要指代那些應用運行時動態(tài)加載的模塊,Webpack會為我們創(chuàng)建類似于JSONP這樣合適的加載器來進行動態(tài)加載。
- 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實現(xiàn)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中,我們同樣需要沖定義組件加載函數(shù):
- 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 默認渲染函數(shù)
- */
- render() {
- return (
- <div
- ref={(ref) => this.props.$(ref).css('background-color', 'red')}>
- jQuery加載完畢
- </div>
- );
- }
- }
- export default LazilyLoadFactory(
- LoadedJQuery,
- {
- $: () => System.import('jquery'),
- }
- );
這里我們將加載完畢的jQuery作為組件的Props參數(shù)傳入到組件中使用,同樣我們也可以使用這種方式加載我們自定義的函數(shù)或者組件。上述兩種的效果如下所示:
【本文是51CTO專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉載請通過51CTO與作者聯(lián)系】