react-router4代碼分割
react-router4官方文檔: https://reacttraining.com/react-router/web/guides/code-splitting
對(duì)于SPA項(xiàng)目,出于加載速度的考慮,肯定不能在頁(yè)面初始化的時(shí)候加載全部js文件,因此需要將代碼分塊收苏,也就是所謂的code splitting,我這里基于react-router4.1.1展示一個(gè)code split的例子夸赫,當(dāng)然棵里,你也可以參考react官方文檔
思路解析
react-router4是對(duì)之前react-router的一次大改垮兑,按照官方的說(shuō)法诉瓦,是將路由的問(wèn)題轉(zhuǎn)變成了react組件的問(wèn)題川队,所以在react-router4里面,不同于以往使用require.ensure睬澡,我們使用一些其他辦法異步的請(qǐng)求組件的js文件固额。
方案A 使用bundle-loader
bundle-loader git地址:https://github.com/webpack-contrib/bundle-loader
先仿著官方的示例先寫一個(gè)Bundle組件,簡(jiǎn)化了一下煞聪,大概會(huì)是這樣的
{/*
// 調(diào)用示例
<Bundle load={require('bundle-loader?lazy!./somefile.js')}>
{(Cmp) => <Cmp></Cmp>}
</Bundle>
*/}
// Bundle.js
import React, { Component } from 'react'
class Bundle extends Component {
constructor() {
super()
this.state = {
mod: null
}
}
componentDidMount() {
this.props.load((mod) => {
this.setState({
mod: mod.default || mod
})
})
}
render() {
return (
this.state.mod ? this.props.children(this.state.mod) : null
)
}
}
export default Bundle
在被傳入的load方法被調(diào)用的時(shí)候斗躏,相應(yīng)的js文件才會(huì)被請(qǐng)求和加載
然后在入口的路由文件里面這樣寫(假設(shè)我們有兩個(gè)組件,Cp1, Cp2)
import React from 'react'
import {
BrowserRouter,
Route
} from 'react-router-dom'
import Bundle from './bundle'
let CodeSplit = () => {
return (
<BrowserRouter>
<div>
<Route path={'/cp1'} render={() => {
return (<Bundle load={require('bundle-loader?lazy!./cp1')}>
{(Cp1) => <Cp1></Cp1>}
</Bundle>)
}
}></Route>
<Route path={'/cp2'} render={() => {
return (<Bundle load={require('bundle-loader?lazy!./cp2')}>
{(Cp2) => <Cp2></Cp2>}
</Bundle>)
}
}></Route>
</div>
</BrowserRouter>
)
}
export default CodeSplit
這樣,代碼分割就完成了昔脯。
注意
這里有一個(gè)小坑瑟捣,如果你跟我一樣使用的是create-react-app的話馋艺,你會(huì)發(fā)現(xiàn)栅干,在運(yùn)行代碼的時(shí)候迈套,會(huì)報(bào)這個(gè)錯(cuò)誤
Line 35: Unexpected '!' in 'bundle-loader?lazy!./cp1'. Do not use import syntax to configure webpack loaders import/no-webpack-loader-syntax
Line 42: Unexpected '!' in 'bundle-loader?lazy!./cp2'. Do not use import syntax to configure webpack loaders import/no-webpack-loader-syntax
這是因?yàn)閏reate-react-app不支持webpack-loader,具體的可以看看這個(gè)issue
why I can't use bundle-loader like this: https://github.com/facebookincubator/create-react-app/issues/2477
解決辦法也很簡(jiǎn)單碱鳞,采用方案B
方案B 使用import()
import() 屬于es的一個(gè)proposal桑李,也就是提案,還沒(méi)有正式立項(xiàng)窿给,所以具體會(huì)有什么問(wèn)題我這里也不清楚贵白,不過(guò)babel已經(jīng)支持,所以我們這里可以嘗試使用崩泡,將之前使用bundle-loader的例子改造一下
因?yàn)閕mport返回一個(gè)promise禁荒,所以我們這里將componentDidMount變成一個(gè)async函數(shù)
{/*
// 調(diào)用方法
<Bundle load={() => import(./somefile.js)}></Bundle>
*/}
import React, { Component } from 'react'
class Bundle extends Component {
constructor() {
super()
this.state = {
mod: null
}
}
async componentDidMount() {
const {default: mod} = await this.props.load()
this.setState({
mod: mod.default || mod
})
}
render() {
return (
this.state.mod ? this.props.children(this.state.mod) : null
)
}
}
export default Bundle
然后在入口文件的路由里面這么用
import React from 'react'
import {
BrowserRouter,
Route
} from 'react-router-dom'
import Bundle from './bundle'
let CodeSplit = () => {
return (
<BrowserRouter>
<div>
<Route path={'/cp1'} render={() => {
return (<Bundle load={() => import('./cp1')}>
{(Cp1) => <Cp1></Cp1>}
</Bundle>)
}
}></Route>
<Route path={'/cp2'} render={() => {
return (<Bundle load={() => import('./cp2')}>
{(Cp2) => <Cp2></Cp2>}
</Bundle>)
}
}></Route>
</div>
</BrowserRouter>
)
}
export default CodeSplit
OK,一個(gè)大致的代碼分割功能差不多就完成了
*************** 2017.12.7 更新 ****************
方案B改進(jìn)版
方案B雖然實(shí)現(xiàn)了我們異步加載組件的需求,但是調(diào)用還是顯得比較麻煩角撞,我們需要一種更優(yōu)雅的方式來(lái)實(shí)現(xiàn)異步加載呛伴,同時(shí)還希望能傳遞參數(shù)給組件和自定義組件在加載時(shí)候的顯示效果,所以這里對(duì)方案B進(jìn)一步進(jìn)行封裝
因?yàn)榇a比較簡(jiǎn)單谒所,所以我這里直接把我項(xiàng)目里的代碼貼過(guò)來(lái)了
// async-component.js
/**
* 用于react router4 code splitting
*/
import React, {Component} from 'react'
/**
* @param {Function} loadComponent e.g: () => import('./component')
* @param {ReactNode} placeholder 未加載前的占位
*/
export default (loadComponent, placeholder = null) => {
class AsyncComponent extends Component {
unmount = false
constructor() {
super()
this.state = {
component: null
}
}
componentWillUnmount() {
this.unmount = true
}
async componentDidMount() {
const {default: component} = await loadComponent()
if(this.unmount) return
this.setState({
component: component
})
}
render() {
const C = this.state.component
return (
C ? <C {...this.props}></C> : placeholder
)
}
}
return AsyncComponent
}
整體思路和之前的代碼是一致的
然后調(diào)用的時(shí)候只需這么寫
Demo組件热康,就是一個(gè)簡(jiǎn)單的無(wú)狀態(tài)組件
// demo.jsx
import React from 'react'
const Demo = () => {
return (
<div>demo</div>
)
}
export default Demo
調(diào)用示例
import asyncComponent from './async-component'
// 獲取到異步組件
const AsyncDemo = asyncComponent(() => import('./demo'))
//...
render() {
return (
<Route path="/demo" component={AsyncDemo}></Route>
)
}
// 如果要傳參
render() {
return (
<Route path="/demo" render={() => {
<AsyncComponent test="hello"></AsyncComponent>
}}></Route>
)
}
參數(shù)也可以通過(guò)asyncComponent函數(shù)進(jìn)行傳遞,不過(guò)需要更改下async-component.js的代碼劣领,因?yàn)楸容^簡(jiǎn)單姐军,所以這里就不展示了