前端性能優化之按需加載(React-router+webpack)

1、什麼是按需加載

和異步加載script的目的同樣(異步加載script的方法),按需加載/代碼切割也能夠解決首屏加載的速度。javascript

  • 何時須要按需加載

若是是大文件,使用按需加載就十分合適。好比一個近1M的全國城市省市縣的json文件,在我首屏加載的時候並不須要引入,而是當用戶點擊選項的時候才加載。若是不點擊,則不會加載。就能夠縮短首屏http請求的數量以及時間。css

若是是小文件,能夠沒必要太在乎按需加載。過多的http請求會致使性能問題。html

2、實現按需加載的方法

  1. Webpack打包模塊工具實現
  2. RequireJs實現

這裏介紹React-router+Webpack實現按需加載的功能,效果以下:
按需加載效果圖java

3、實現過程(React-router4.0)

注意!我這裏使用的是最新版本的React-router-dom^4.3.1.若是是4.0如下的react-route可直接看四react

4.0相比之前實現方式要複雜。須要引入bundle-loader模塊。而且本身建立bundle模型實現。webpack

1.建立包裝組件模型bundle.js
import React from 'react';

class Bundle extends React.Component {
    constructor(arg){
        super(arg)
        this.state = {
            mod: null,
        }
    }
    
    componentWillMount() {
        this.load(this.props);
    }
    componentWillReceiveProps(nextProps) {
        if (nextProps.load !== this.props.load) {
            this.load(nextProps);
        }
    }
    // load 方法,用於更新 mod 狀態
    load(props) {
        // 初始化
        this.setState({
            mod: null
        });
        /*
            調用傳入的 load 方法,並傳入一個回調函數
            這個回調函數接收 在 load 方法內部異步獲取到的組件,並將其更新爲 mod 
        */ 
        props.load(mod => {
            this.setState({
            mod: mod.default ? mod.default : mod
            });
        });
    }

    render() {
        /*
            將存在狀態中的 mod 組件做爲參數傳遞給當前包裝組件的'子'
        */ 
        return this.state.mod ? this.props.children(this.state.mod) : null;
    }
}

export default Bundle ;
2.建立包裝組件的方法(函數)
// 懶加載方法
import React from 'react';
import Bundle from './Bundle';

console.log(Bundle);
// 默認加載組件,能夠直接返回 null
const Loading = () => <div>Loading...</div>;

/*
   包裝方法,第一次調用後會返回一個組件(函數式組件)
   因爲要將其做爲路由下的組件,因此須要將 props 傳入
*/

const lazyLoad = loadComponent => props => (
   <Bundle load={loadComponent}>
      {Comp => (Comp ? <Comp {...props} /> : <Loading />)}
   </Bundle>
);

console.log(lazyLoad);
export default lazyLoad;    //實際上lazyLoad就是一個函數,組件調用便可

上面兩個文件的關係:
lazyLoad.js從名字上看,叫懶加載.其實是一箇中間件的做用。最後lazyLoad會暴露一個函數出來供組件調用。lazyLoad導出的內容:web

function lazyLoad(loadComponent) {
    return function(props) {
        return (
            <Bundle load={loadComponent}>
                {Comp => (Comp ? <Comp {...props} /> : <Loading />)}
            </Bundle>
        )
    }
}

顯而易見,loadComponent就是要加載的組件,在路由中調用,例如:異步調用page1組件json

<Route path="/page1" component={lazyLoad(Page1)}/>

Bundle.js做爲按需加載的核心,在lazyLoad中間件就已經引入,並傳入一個自定義的方法load,值爲組件內容。以及動態的子內容children:react-router

{Comp => (Comp ? <Comp {...props} /> : <Loading />)}

最終返回組件信息,並附帶相應的props.若是不存在相關組件,則Loading
bundleapp

3. Route搭配使用
import React from 'react';
import { NavLink,Route,Switch,BrowserRouter as Router } from 'react-router-dom'
import './style/style.css'
import 'bundle-loader'
// bundle模型用來異步加載組件
import Bundle from '../routes/Bundle.js';
import lazyLoad from '../routes/lazyLoad';

import Page1 from 'bundle-loader?lazy&name=page1!../components/page1/index';
import Page2 from 'bundle-loader?lazy&name=page2!../components/page2/index';
import Page3 from 'bundle-loader?lazy&name=page3!../components/page3/index';

class AppPage extends React.Component{
    constructor(arg){
        super(arg)

        this.state={}
    }
    render(){
        return(
            <Router  basename="/" >
                <div className="appWried">
                    <div className="appBtn">
                        <NavLink to="/page1" className="button" activeClassName="active">
                            PAGE1
                        </NavLink>
                        <NavLink to="/page2" className="button" activeClassName="active">
                            PAGE2
                        </NavLink>
                        <NavLink to="/page3" className="button" activeClassName="active">
                            PAGE3
                        </NavLink>
                    </div>
                    <Route
                        path="/"
                        render={props => (
                            <Switch>
                                <Route path="/page1" component={lazyLoad(Page1)}/> 
                                <Route path="/page2" component={lazyLoad(Page2)}/> 
                                <Route path="/page3" component={lazyLoad(Page3)}/> 
                            </Switch>
                        )}
                    />
                </div>
            </Router>
        )
    }
}

export default AppPage;
  • 幾個注意的點:
    1. import異步加載組件的時候,名字變動爲'bundle-loader?lazy&name=page1!../components/page1/index'

    其中bundle-loader表示loader:'bunle-loader'(需加載bundle-loader模塊)
    lazy表示lazy:true;懶加載
    name:表示異步生成的文件名字

    1. 去掉外層route,不用render渲染也是可行的
    <Switch>
        <Route path="/page1" component={lazyLoad(Page1)}/> 
        <Route path="/page2" component={lazyLoad(Page2)}/> 
        <Route path="/page3" component={lazyLoad(Page3)}/> 
    </Switch>
//webpack.config.js
...
    module.exports = {
        ...
        output:{
            path:path.join(__dirname + '/dist'),    //打包地方
            filename:'bundle.js',    //打包名字
            publicPath: '/',    //自動生成html引入js的路徑
            //按需加載
            chunkFilename:'[name]_[chunkhash:8].js'
        },
        ...
    }
...

這裏要注意一下publicPath這個參數.
若是未設置publicPath參數,則默認打包生成的html引入bundle的時候爲:

<script type="text/javascript" src="bundle.js"></script>

若是設置publicPath爲publicPath: '/dist',則打包後html文件引入js格式爲:

<script type="text/javascript" src="/dist/bundle.js"></script>

參考文章:React-router-v4 - Webpack 實現按需加載(code-splitting)

相關文章
相關標籤/搜索