react-router4的好處究竟在哪里
最近被人問(wèn)到你的項(xiàng)目中為什么要使用react-router v4,瞬間有點(diǎn)懵逼?;卮鹫f(shuō),幫我們實(shí)現(xiàn)路由啊,被反問(wèn),僅僅是為了幫助實(shí)現(xiàn)路由嗎,自己也可以手寫(xiě)一套路由啊,沒(méi)有什么難度啊,為什么要使用別人的呢。當(dāng)他問(wèn)到這的時(shí)候,我就知道問(wèn)到盲區(qū)了。于是晚上空閑的時(shí)候我仔細(xì)研究和查閱了一下,說(shuō)實(shí)話,react-router不知道已經(jīng)用了多少次了??梢哉f(shuō),里面的源碼我都已經(jīng)吃透了,但是竟然從來(lái)沒(méi)有想過(guò)react-router的優(yōu)點(diǎn)在哪里,這點(diǎn)確實(shí)是值得反思的
react-router v4與react-router v3的比較
查閱文檔及翻看博客發(fā)現(xiàn),React Router v4 最大的變更,不是API的變更,而是從靜態(tài)路由到動(dòng)態(tài)路由的變化。什么是靜態(tài)路由呢?靜態(tài)路由是一堆在應(yīng)用運(yùn)行前就已經(jīng)定義好的路由配置,應(yīng)用需要在啟動(dòng)時(shí),加載這些配置,構(gòu)建出整個(gè)應(yīng)用的路由表,然后當(dāng)接收到某一請(qǐng)求時(shí),根據(jù)請(qǐng)求地址,到應(yīng)用路由表中找到對(duì)應(yīng)的處理頁(yè)面或處理方法。就是說(shuō)路由一定要集中配置,沒(méi)有集中配置的路由無(wú)法找到匹配頁(yè)面。不管是前端開(kāi)發(fā),還是后端開(kāi)發(fā),只要涉及到路由,大部分情況下,其實(shí)我們使用的都是靜態(tài)路由。例如,React Router v3版本中,我們會(huì)配置一個(gè)類似下面形式的路由:
<Router history={browserHistory}>
<Route path='/' component={login}>
<Route path='about' component={About}>
<Route path='contact' component={Contact}>
// ...
</Route>
</Router>
它的基本工作流程是:Router組件根據(jù)所有的子組件Route,生成全局的路由表,路由表中記錄了path與UI組件的映射關(guān)系,然后Router監(jiān)聽(tīng)path的變化,當(dāng)path變化時(shí),根據(jù)新的path,找出對(duì)應(yīng)的component,按一定層級(jí)將這些UI組件渲染出來(lái)。
這種方式看起來(lái)簡(jiǎn)單明了,理解起來(lái)也非常的輕松。但是這種實(shí)現(xiàn)方式是違背了react的組件化思想的。react提倡一切皆組件,組件是什么,對(duì)應(yīng)到頁(yè)面上它就是頁(yè)面的一部分,也就是UI。但是,v3的route組件只是包了一層react的殼子,實(shí)際上它并沒(méi)有渲染組件,這點(diǎn)我們可以通過(guò)它的源碼就可以看出來(lái):
const Route = createReactClass({
// 省略無(wú)關(guān)代碼
/* istanbul ignore next: sanity check */
render() {
invariant(
false,
'<Route> elements are for router configuration only and should not be rendered'
)
}
})
可以發(fā)現(xiàn)render里面并沒(méi)有渲染任何東西,所以說(shuō)它不是傳統(tǒng)意義上的react組件。當(dāng)然,router的開(kāi)發(fā)者也意識(shí)到了這個(gè)問(wèn)題,所以在v4中,對(duì)React Router 進(jìn)行了重寫(xiě),將Route作為普通React組件看待,每個(gè)Route也負(fù)責(zé)UI的渲染工作,讓React Router在React的大框架下運(yùn)轉(zhuǎn)。我們用v4版本實(shí)現(xiàn)一個(gè)路由:
<BrowserRouter>
<Switch>
<Route exact path="/login" render={() => <LoginView />} />
<Route
path="/:tabIndex/:module"
render={() => <HomeComponent />}
/>
<Route
path="/"
render={() => <RedirectComponent moduleName="community" />}
/>
</Switch>
</BrowserRouter>
看起來(lái)寫(xiě)法跟上面的那個(gè)例子沒(méi)有什么大的區(qū)別,所以說(shuō)它這個(gè)路由升級(jí)還是比較友好的。但是深層次的東西是有巨大的改變的,我們?cè)賯z看一下route的實(shí)現(xiàn):
class Route extends React.Component {
//...省略無(wú)關(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
)
)
}
}
可以發(fā)現(xiàn)每一個(gè)router對(duì)應(yīng)的都會(huì)渲染對(duì)應(yīng)的組件,macth方法會(huì)自動(dòng)給我們計(jì)算,如果沒(méi)有匹配的。則返回一個(gè)null。這種模式的路由就是動(dòng)態(tài)路由。可見(jiàn),動(dòng)態(tài)路由發(fā)揮作用的時(shí)間是在組件渲染時(shí),而不是通過(guò)提前配置的方式,在應(yīng)用剛收到請(qǐng)求時(shí),就已經(jīng)知道該渲染哪些組件了。
從上面的分析,可以得出動(dòng)態(tài)路由的一個(gè)優(yōu)點(diǎn)是,它會(huì)同時(shí)負(fù)責(zé)UI的渲染工作,而不是單純的路由配置工作。此外,動(dòng)態(tài)路由的另外一個(gè)優(yōu)點(diǎn)是,你可以在任意時(shí)間、任意地點(diǎn)自由添加新的Route。例如,在上面的例子中,我想在后面定義更多的頁(yè)面,你想在哪都可以,在主頁(yè)面或者其他地方都可以,它會(huì)將route匹配渲染的組件作為當(dāng)前組件的一部分進(jìn)行渲染。非常的方便,下面是一個(gè)例子:
class Layout extends React.Component {
render(){ return
(<div className="Layout-content">
<div className="Layout-header-part">
<div className="header-right-part">
<MenuList
menuList={menuList}
activeMenu={activeMenu}
menuClick={this.menuClick.bind(this)}
/>
<UserAction
//userInfo={UserStore.userInfo}
userPopTolls={this.userPopTolls}
logOut={this.logOut.bind(this)}
showAlarmAction={this.showAlarmAction.bind(this)}
clickPopAction={this.clickPopAction.bind(this)}
//ifShake={ifShake}
/>
</div>
</div>
{/* <MenuList menuClick={this.menuClick} activeMenu={activeMenu}/> */}
<Switch>
{menuList.map((v, i) => <Route key={i} path={`/mainPage/${v.name}`} render={() => <v.component />} />)}
</Switch>
</div>)
}}
我們直接在layout里面定義了我們需要的路由,V4方便的地方就是我們可以將路由寫(xiě)在一個(gè)文件里面隨意的配置改動(dòng),非常的方便。
總結(jié)一下,雖然React Router v4 重構(gòu)了路由使用的思想,但卻和React的設(shè)計(jì)思想更加切合,個(gè)人認(rèn)為是一個(gè)巨大的進(jìn)步。使用React Router v4 時(shí),你需要忘掉以前使用靜態(tài)路由的思維方式,把路由當(dāng)成普通組件看待,習(xí)慣了這個(gè)思維轉(zhuǎn)變后,你就會(huì)發(fā)現(xiàn)React Router v4的魅力所在了。