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本大前端精選書籍随静!