萬惡的根源
距離React Router v4 正式發布也已經過去三個月了,這周把一個React的架子做了升級,之前的路由用的還是v2.7.0版的,所以決定把路由也升級下,正好“嘗嘗鮮”...
江湖傳言,目前官方同時維護 2.x 和 4.x 兩個版本。(ヾ(??﹏?)??咦,此刻相信機智如我的你也會發現,ReactRouter v3 去哪兒了?整丟了??巴拉出鍋了???敢不敢給我個完美的解釋!?)事實上 3.x 版本相比于 2.x 并沒有引入任何新的特性,只是將 2.x 版本中部分廢棄 API 的 warning 移除掉而已。按照規劃,沒有歷史包袱的新項目想要使用穩定版的 ReactRouter 時,應該使用 ReactRouter 3.x。目前 3.x 版本也還處于 beta 階段,不過會先于 4.x 版本正式發布。如果你已經在使用 2.x 的版本,那么升級 3.x 將不會有任何額外的代碼變動。
禮貌性簡介下
React Router V4 相較于前面三個版本有根本性變化,首先是遵循Just Component的 API 設計理念,其次API方面也精簡了不少,對新手來說降低了學習難度,但如果是對之前項目的重構,嗯,簡直無**可說。本次升級的主要特點如下:
- 聲明式(Declarative)
- 可組合 (Composability)
React Router V4 遵循了 React 的理念:萬物皆組件。因此 升級之后的 Route、Link、Switch等都是一個普通的組件。
React Router V4 基于 Lerna 管理多個 Repository。在此代碼庫包括:
- react-router React Router 核心
- react-router-dom 用于 DOM 綁定的 React Router
- react-router-native 用于 React Native 的 React Router
- react-router-redux React Router 和 Redux 的集成
- react-router-config 靜態路由配置幫助助手
插件初引入
通常我們在 React 的使用中,一般要引入兩個包,react
和 react-dom
,那么react-router
和react-router-dom
是不是兩個都要引用呢?注意,前方高能,入門第一坑就在這里。他們兩個只要引用一個就行了,不同之處就是后者比前者多出了<Link> <BrowserRouter>
這樣的 DOM 類組件。因此我們只需引用react-router-dom
這個包就OK了。當然,如果搭配redux
,你還需要使用react-router-redux
。
主要組件簡介
<Router>
在4.0之前版本的 API 中,<Router>
組件的 children 只能是 React Router 提供的各種組件,如<Route>、<IndexRoute>、<Redirect>
等。而在 React Router 4 中,你可以將各種組件及標簽放進 <Router>
組件中,他的角色也更像是 Redux 中的 <Provider>
。不同的是<Provider>
是用來保持與 store 的更新,而<Router>
是用來保持與 location 的同步。示例如下:
// 示例1
<Router>
<div>
<ul>
<li><Link to="/">首頁</Link></li>
<li><Link to="/about">關于</Link></li>
<li><Link to="/topics">主題列表</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
Router是所有路由組件共用的底層接口,一般我們的應用并不會使用這個接口,而是使用高級的路由:
<BrowserRouter>
:使用 HTML5 提供的 history API 來保持 UI 和 URL 的同步;<HashRouter>
:使用 URL 的 hash (例如:window.location.hash) 來保持 UI 和 URL 的同步;<MemoryRouter>
:能在內存保存你 “URL” 的歷史紀錄(并沒有對地址欄讀寫);<NativeRouter>
:為使用React Native提供路由支持;<StaticRouter>
:從不會改變地址;
TIPS:算是第二坑吧,和之前的Router不一樣,這里<Router>
組件下只允許存在一個子元素,如存在多個則會報錯。
反面典型在這里:
// 示例2
<Router>
<ul>
<li><Link to="/">首頁</Link></li>
<li><Link to="/about">關于</Link></li>
<li><Link to="/topics">主題列表</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</Router>
沒錯,示例2在沒有<div>
爸爸的保護下,會報如下異常信息:
<Route>
我們知道,Route組件主要的作用就是當一個location匹配路由的path時,渲染某些UI。示例如下:
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/news" component={NewsFeed}/>
</div>
</Router>
// 如果應用的地址是/,那么相應的UI會類似這個樣子:
<div>
<Home/>
</div>
// 如果應用的地址是/news,那么相應的UI就會成為這個樣子:
<div>
<NewsFeed/>
</div>
<Route>
組件有如下屬性:
- path(string): 路由匹配路徑。(沒有path屬性的Route 總是會 匹配);
- exact(bool):為true時,則要求路徑與location.pathname必須完全匹配;
- strict(bool):true的時候,有結尾斜線的路徑只能匹配有斜線的location.pathname;
再次奉上兩個鮮活的例子:
exact配置:
路徑 | location.pathname | exact | 是否匹配 |
---|---|---|---|
/one | /one/two | true | 否 |
/one | /one/two | false | 是 |
strict配置:
路徑 | location.pathname | strict | 是否匹配 |
---|---|---|---|
/one/ | /one | true | 否 |
/one/ | /one/ | true | 是 |
/one/ | /one/two | true | 是 |
同時,新版的路由為<Route>
提供了三種渲染內容的方法:
<Route component>
:在地址匹配的時候React的組件才會被渲染,route props也會隨著一起被渲染;<Route render>
:這種方式對于內聯渲染和包裝組件卻不引起意料之外的重新掛載特別方便;<Route children>
:與render屬性的工作方式基本一樣,除了它是不管地址匹配與否都會被調用;
第一種方式沒啥可說的,和之前一樣,這里我們重點看下<Route render>
的渲染方式:
// 行內渲染示例
<Route path="/home" render={() => <div>Home</div>}/>
// 包裝/合成
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)
<FadingRoute path="/cool" component={Something}/>
TIPS: 第三坑! <Route component>
的優先級要比<Route render>
高,所以不要在同一個<Route>
中同時使用這兩個屬性。
<Link>
和之前版本沒太大區別,重點看下組件屬性:
- to(string/object):要跳轉的路徑或地址;
- replace(bool):為 true 時,點擊鏈接后將使用新地址替換掉訪問歷史記錄里面的原地址;為 false 時,點擊鏈接后將在原有訪問歷史記錄的基礎上添加一個新的紀錄。默認為 false;
示例如下:
// Link組件示例
// to為string
<Link to="/about">關于</Link>
// to為obj
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}}/>
// replace
<Link to="/courses" replace />
<NavLink>
<NavLink>
是<Link>
的一個特定版本, 會在匹配上當前 URL 的時候會給已經渲染的元素添加樣式參數,組件屬性:
- activeClassName(string):設置選中樣式,默認值為 active;
- activeStyle(object):當元素被選中時, 為此元素添加樣式;
- exact(bool):為 true 時, 只有當地址完全匹配 class 和 style 才會應用;
- strict(bool):為 true 時,在確定位置是否與當前 URL 匹配時,將考慮位置 pathname 后的斜線;
- isActive(func):判斷鏈接是否激活的額外邏輯的功能;
從這里我們也可以看出,新版本的路由在組件化上面確實下了不少功夫,來看看NavLink的使用示例:
// activeClassName選中時樣式為selected
<NavLink
to="/faq"
activeClassName="selected"
>FAQs</NavLink>
// 選中時樣式為activeStyle的樣式設置
<NavLink
to="/faq"
activeStyle={{
fontWeight: 'bold',
color: 'red'
}}
>FAQs</NavLink>
// 當event id為奇數的時候,激活鏈接
const oddEvent = (match, location) => {
if (!match) {
return false
}
const eventID = parseInt(match.params.eventID)
return !isNaN(eventID) && eventID % 2 === 1
}
<NavLink
to="/events/123"
isActive={oddEvent}
>Event 123</NavLink>
<Switch>
該組件用來渲染匹配地址的第一個<Route>
或者<Redirect>
。那么它與使用一堆route又有什么區別呢?
<Switch>
的獨特之處是獨它僅僅渲染一個路由。相反地,每一個包含匹配地址(location)的<Route>
都會被渲染。思考下面的代碼:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
如果現在的URL是/about
,那么<About>
, <User>
, 還有<NoMatch>
都會被渲染,因為它們都與路徑(path)匹配。這種設計,允許我們以多種方式將多個<Route>
組合到我們的應用程序中,例如側欄(sidebars),面包屑(breadcrumbs),bootstrap tabs等等。 然而,偶爾我們只想選擇一個<Route>
來渲染。如果我們現在處于/about
,我們也不希望匹配/:user
(或者顯示我們的 “404” 頁面 )。以下是使用 Switch 的方法來實現:
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
現在,如果我們處于/about
,<Switch>
將開始尋找匹配的<Route>
。<Route path="/about"/>
將被匹配, <Switch>
將停止尋找匹配并渲染<About>
。同樣,如果我們處于/michael
,<User>
將被渲染。
以上就是我對React Router v4 的初試,反正也是一邊查文檔,一邊試水的,如有錯誤或疏漏,還請大家諒解并不吝指正!