聊聊 React Router v4 的設(shè)計思想

React Router v4 發(fā)布已經(jīng)有幾個月了鸯乃,但好像并沒有得到太多人的青睞剖张,大家(包括我們團隊自己)還是習(xí)慣使用v2派阱、v3版本诬留。這一方面是因為v4版本是一次破壞性的升級,從v2贫母、v3 升級到v4文兑,必需要大量重寫原有的路由相關(guān)的代碼,對于已經(jīng)穩(wěn)定的項目腺劣,一般是不會輕易嘗試這種變更的绿贞;另一方面,即使是新項目橘原,很多開發(fā)者也依然選擇使用v2籍铁、v3老版本,因為v4新的設(shè)計思想趾断,意味著你必須改變原有的使用路由的思維拒名,才能正確的使用新版本。

React Router v4 最大的變更芋酌,不是API的變更靡狞,而是從靜態(tài)路由到動態(tài)路由的變化。什么是靜態(tài)路由呢隔嫡?靜態(tài)路由是一堆在應(yīng)用運行前就已經(jīng)定義好的路由配置甸怕,應(yīng)用需要在啟動時,加載這些配置腮恩,構(gòu)建出整個應(yīng)用的路由表梢杭,然后當(dāng)接收到某一請求時,根據(jù)請求地址秸滴,到應(yīng)用路由表中找到對應(yīng)的處理頁面或處理方法武契。不管是前端開發(fā),還是后端開發(fā),只要涉及到路由咒唆,大部分情況下届垫,其實我們使用的都是靜態(tài)路由。例如全释,React Router v3版本中装处,我們會配置一個類似下面形式的路由:

<Router history={browserHistory}>
  <Route path='/' component={App}>
    <Route path='about' component={About}>
    <Route path='contact' component={Contact}>
    // ...
  </Route>
</Router>

它的基本工作流程是:Router組件根據(jù)所有的子組件Route,生成全局的路由表浸船,路由表中記錄了path與UI組件的映射關(guān)系妄迁,然后Router監(jiān)聽path的變化,當(dāng)path變化時李命,根據(jù)新的path登淘,找出對應(yīng)所需的所有UI組件,按一定層級將這些UI組件渲染出來封字。

對于已經(jīng)很熟悉靜態(tài)路由使用方式的開發(fā)者來說黔州,上面的工作流程顯得很自然,理解起來也毫不費力阔籽。既然如此流妻,React Router的作者為什么還要把這一切推翻呢?原因是React Router不是普通的Router仿耽,它是“React”的Router合冀。React致力于提供一個高效簡潔的組件化方案各薇,組件就是React的核心项贺,在React的設(shè)計思想中,一切皆是組件峭判。那么什么是組件呢开缎?組件定義的是界面上一個區(qū)域的UI及UI的交互行為,關(guān)注點是UI×煮Γ現(xiàn)在讓我們回頭來看看上面靜態(tài)路由的例子奕删,是不是感覺到什么奇怪的地方呢?雖然Route形式上是React組件疗认,但它其實與UI無任何關(guān)系完残,它只是披著React組件的外衣,提供了一條路由配置項而已横漏。我們也可以從Route源碼中看出這一點:

const Route = createReactClass({
  // 省略無關(guān)代碼

  /* istanbul ignore next: sanity check */
  render() {
    invariant(
      false,
      '<Route> elements are for router configuration only and should not be rendered'
    )
  }

})

Route的render方法中谨设,沒有做任何UI渲染相關(guān)的工作,這確實不是一個正宗的React組件缎浇。當(dāng)然你也可以用React Router的另一種配置路由的方式:

const routes = {
  path: '/',
  component: App,
  childRoutes: [
    {
      path: 'about',
      component: About,
    },
    {
      path: 'contact',
      component: Contact,
    },
    // ...
  ]
}

<Router history={browserHistory} routes={routes} />

現(xiàn)在你又可以理直氣壯的說扎拣,我沒有使用Route這個偽組件了,這次和React的設(shè)計思想沒有沖突了吧?好吧二蓝,讓我們再來看看其他部分誉券。React Router v3提供了很多類似生命周期方法的API,例如onEnter, onUpdate, and onLeave 刊愚,用來為處于不同階段的路由提供鉤子方法踊跟。但是,請不要忘了百拓,React組件本身已經(jīng)有一套很完善的生命周期方法了琴锭,如果一個Route就是一個組件,那么我們完全可以直接利用組件的生命周期方法衙传,來作為路由不同階段的鉤子方法决帖。例如,我們可以使用componentDidMount 或 componentWillMount替代onEnter蓖捶,使用 componentDidUpdate或 componentWillUpdate 替代onUpdate地回,使用componentWillUnmount替代onLeave。

React Router v2俊鱼、v3的問題刻像,是在React組件思想之外,設(shè)計了一套API并闲,是一種侵入式的設(shè)計细睡。React Router的作者意識到了這個問題,所以在v4中帝火,對React Router 進行了重寫溜徙,將Route作為普通React組件看待,每個Route也負責(zé)UI的渲染工作犀填,讓React Router在React的大框架下運轉(zhuǎn)蠢壹。我們用v4版本實現(xiàn)上面的例子:

<BrowserRouter>
  <div>
    <Route path='/' component={App} />
    <Route path={'/about'} component={About} />
    <Route path={'/contact'} component={Contact} />
  </div>
</BrowserRouter>

但從表面上看,并不能很直觀地看出Route工作機制的變化九巡。這里做一簡單說明:Route的作用不是提供路由配置图贸,而是一個普通的UI組件,不管請求的路徑是什么冕广,Route組件總是會被渲染疏日,只不過在Route內(nèi)部會判斷請求路徑是否與當(dāng)前的path匹配,如果匹配撒汉,就會把Route component屬性指向的組件作為子組件渲染出來沟优,如果不匹配,會渲染一個null神凑【簧瘢可以從新版Route 的render方法源碼中印證這個流程:

class Route extends React.Component {
  //...省略無關(guān)代碼
  
  render() {
    const { match } = this.state
    const { children, component, render } = this.props
    const { history, route, staticContext } = this.context.router
    const location = this.props.location || route.location
    const props = { match, location, history, staticContext }

    return (
      component ? ( // component prop gets first priority, only called if there's a match
        match ? React.createElement(component, props) : null
      ) : render ? ( // render prop is next, only called if there's a match
        match ? render(props) : null
      ) : children ? ( // children come last, always called
        typeof children === 'function' ? (
          children(props)
        ) : !Array.isArray(children) || children.length ? ( // Preact defaults to empty children array
          React.Children.only(children)
        ) : (
          null
        )
      ) : (
        null
      )
    )
  }
}

這種模式的路由就是動態(tài)路由何吝。可見鹃唯,動態(tài)路由發(fā)揮作用的時間是在組件渲染時爱榕,而不是通過提前配置的方式,在應(yīng)用剛收到請求時坡慌,就已經(jīng)知道該渲染哪些組件了黔酥。

從上面的分析,可以得出動態(tài)路由的一個優(yōu)點是洪橘,它會同時負責(zé)UI的渲染工作跪者,而不是單純的路由配置工作。此外熄求,動態(tài)路由的另外一個優(yōu)點是渣玲,你可以在任意時間、任意地點自由添加新的Route弟晚。例如忘衍,在上面的例子中,我想在About組件內(nèi)定義兩個新的路由卿城,可以這么做:

<BrowserRouter>
  <div>
    <Route path='/' component={App} />
    <Route path={'/about'} component={About} />
    <Route path={'/contact'} component={Contact} />
  </div>
</BrowserRouter>

const About = (props) => (
  <div>
    <Route path={`${props.match.url}/a`} component={AboutA} />
    <Route path={`${props.match.url}/b`} component={AboutB} />
  </div>
)

這樣枚钓,當(dāng)訪問 /about/a 時,組件AboutA 會被作為About的子組件渲染瑟押,當(dāng)訪問 /about/b 時搀捷,組件AboutB 會作為About的子組件渲染。而且多望,/about/a 和 /about/b 我們是直接定義到 About 組件內(nèi)的嫩舟,并不需要像靜態(tài)路由那樣做集中配置,充分體現(xiàn)了動態(tài)路由的靈活性便斥。

總結(jié)一下至壤,雖然React Router v4 重構(gòu)了路由使用的思想威始,但卻和React的設(shè)計思想更加切合枢纠,個人認為是一個巨大的進步。使用React Router v4 時黎棠,你需要忘掉以前使用靜態(tài)路由的思維方式晋渺,把路由當(dāng)成普通組件看待,習(xí)慣了這個思維轉(zhuǎn)變后脓斩,你就會發(fā)現(xiàn)React Router v4的魅力所在了木西。


歡迎關(guān)注我的公眾號:老干部的大前端,領(lǐng)取21本大前端精選書籍随静!

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末八千,一起剝皮案震驚了整個濱河市吗讶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恋捆,老刑警劉巖照皆,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沸停,居然都是意外死亡膜毁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門愤钾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘟滨,“玉大人,你說我怎么就攤上這事能颁≡尤常” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵伙菊,是天一觀的道長胧沫。 經(jīng)常有香客問我,道長占业,這世上最難降的妖魔是什么绒怨? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谦疾,結(jié)果婚禮上南蹂,老公的妹妹穿的比我還像新娘。我一直安慰自己念恍,他們只是感情好六剥,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著峰伙,像睡著了一般疗疟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瞳氓,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天策彤,我揣著相機與錄音,去河邊找鬼匣摘。 笑死店诗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的音榜。 我是一名探鬼主播庞瘸,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赠叼!你這毒婦竟也來了擦囊?” 一聲冷哼從身側(cè)響起违霞,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞬场,沒想到半個月后葛家,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡泌类,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年癞谒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃榨。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡弹砚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枢希,到底是詐尸還是另有隱情桌吃,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布苞轿,位于F島的核電站茅诱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏搬卒。R本人自食惡果不足惜瑟俭,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望契邀。 院中可真熱鬧摆寄,春花似錦、人聲如沸坯门。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽古戴。三九已至欠橘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間现恼,已是汗流浹背肃续。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留述暂,地道東北人痹升。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓建炫,卻偏偏與公主長得像畦韭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肛跌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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