react-router4代碼分割

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)單姐军,所以這里就不展示了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尖淘,隨后出現(xiàn)的幾起案子奕锌,更是在濱河造成了極大的恐慌,老刑警劉巖村生,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惊暴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡梆造,警方通過(guò)查閱死者的電腦和手機(jī)缴守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)镇辉,“玉大人屡穗,你說(shuō)我怎么就攤上這事『龈兀” “怎么了村砂?”我有些...
    開(kāi)封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)屹逛。 經(jīng)常有香客問(wèn)我础废,道長(zhǎng)汛骂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任评腺,我火速辦了婚禮帘瞭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒿讥。我一直安慰自己蝶念,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芋绸。 她就那樣靜靜地躺著媒殉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摔敛。 梳的紋絲不亂的頭發(fā)上廷蓉,一...
    開(kāi)封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音马昙,去河邊找鬼桃犬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛给猾,可吹牛的內(nèi)容都是我干的疫萤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼敢伸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扯饶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起池颈,我...
    開(kāi)封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尾序,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后躯砰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體每币,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年琢歇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兰怠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡李茫,死狀恐怖揭保,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魄宏,我是刑警寧澤秸侣,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響味榛,放射性物質(zhì)發(fā)生泄漏椭坚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一搏色、第九天 我趴在偏房一處隱蔽的房頂上張望善茎。 院中可真熱鬧,春花似錦继榆、人聲如沸巾表。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至考阱,卻和暖如春翠忠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乞榨。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工秽之, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吃既。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓考榨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鹦倚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子河质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容