一、什么是按需加載
和異步加載script的目的一樣(異步加載script的方法),按需加載/代碼切割也可以解決首屏加載的速度。
- 什么時候需要按需加載
如果是大文件,使用按需加載就十分合適。比如一個近1M的全國城市省市縣的json文件,在我首屏加載的時候並不需要引入,而是當用戶點擊選項的時候才加載。如果不點擊,則不會加載。就可以縮短首屏http請求的數量以及時間。
如果是小文件,可以不必太在意按需加載。過多的http請求會導致性能問題。
二、實現按需加載的方法
- Webpack打包模塊工具實現
- RequireJs實現
這里介紹React-router+Webpack
實現按需加載的功能,效果如下:
三、實現過程(React-router4.0)
注意!我這里使用的是最新版本的React-router-dom^4.3.1.如果是4.0以下的react-route可直接看四
4.0相比以前實現方式要復雜。需要引入bundle-loader模塊。並且自己創建bundle模型實現。
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導出的內容:
function lazyLoad(loadComponent) {
return function(props) {
return (
<Bundle load={loadComponent}>
{Comp => (Comp ? <Comp {...props} /> : <Loading />)}
</Bundle>
)
}
}
顯而易見,loadComponent就是要加載的組件,在路由中調用,例如:異步調用page1組件
<Route path="/page1" component={lazyLoad(Page1)}/>
Bundle.js作為按需加載的核心,在lazyLoad中間件就已經引入,並傳入一個自定義的方法load,值為組件內容。以及動態的子內容children:
{Comp => (Comp ? <Comp {...props} /> : <Loading />)}
最終返回組件信息,並附帶相應的props.如果不存在相關組件,則Loading
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;
-
幾個注意的點:
- import異步加載組件的時候,名字變更為'bundle-loader?lazy&name=page1!../components/page1/index'
其中bundle-loader表示loader:'bunle-loader'(需加載bundle-loader模塊)
lazy表示lazy:true;懶加載
name:表示異步生成的文件名字- 去掉外層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>