幾個月前,
React Router 4
發佈,我能清晰地感受到來自大量的修改
的不一樣聲音。誠然,我在學習React Router 4
的第一天,也是很是痛苦
的,可是,這並非由於看它的API
,而是反覆思考使用它的模式
和策略
,由於V4
的變化確實有點大,V3
的功能它都有,除此以外,還增長了一些特性
,我不能直接將使用V3
的心得直接遷移過來,如今,我必須從新審視router
和layout components
之間的關係react
本篇文章不是把 React Router 4
的 API
再次呈現給讀者看,而是簡單介紹其中最經常使用的幾個概念,和重點講解我在實踐的過程當中發現的比較好的 模式
和 策略
瀏覽器
不過,在閱讀下文以前,你得首先保證如下的 概念
對你來講 並不陌生
react-router
React stateless(Functional) 組件
app
ES6 的 箭頭函數
和它的 隱式返回
less
ES6 的 解構
dom
ES6 的 模板字符串
ide
若是你就是那 萬中無一
的絕世高手,那麼你也能夠選擇直接 view demo函數
React Router
的早期版本是將 router
和 layout components
分開,爲了完全搞清楚 V4
究竟有什麼不一樣,咱們來寫兩個簡單的 example
就明白了佈局
example app
就兩個 routes
,一個 home
,一個 user
學習
在 V3
中
import React from "react"; import { render } from "react-dom"; import { Router, Route, IndexRoute, Link, browserHistory } from "react-router"; const PrimaryLayout = props => <div className="primary-layout"> <header>Our React Router 3 App</header> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/user">User</Link> </li> </ul> <main> {props.children} </main> </div>; const HomePage = () => <h1>Home Page</h1>; const UsersPage = () => <h1>User Page</h1>; const App = () => <Router history={browserHistory}> <Route path="/" component={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/user" component={UsersPage} /> </Route> </Router>; render(<App />, document.getElementById("root"));
上篇文章給你們推薦了一個在線
react
編譯器 stackblitz,本篇文章再給你們推薦一個不錯的,codesandbox,專門針對react
且開源,正所謂,實踐是檢驗真理的惟一標準
,這也是一種良好的學習習慣
上面代碼中有幾個關鍵的點在 V4
中就不復存在了
集中式 router
經過 <Route>
嵌套,實現 Layout
和 page 嵌套
Layout
和 page 組件
是做爲 router
的一部分
咱們使用 V4
來實現相同的應用程序對比一下
import React from "react"; import { render } from "react-dom"; import { BrowserRouter, Route, Link } from "react-router-dom"; const PrimaryLayout = () => <div className="primary-layout"> <header>Our React Router 4 App</header> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/User">User</Link> </li> </ul> <main> <Route path="/" exact component={HomePage} /> <Route path="/user" component={UsersPage} /> </main> </div>; const HomePage = () => <h1>Home Page</h1>; const UsersPage = () => <h1>User Page</h1>; const App = () => <BrowserRouter> <PrimaryLayout /> </BrowserRouter>; render(<App />, document.getElementById("root"));
注意,咱們如今
import
的是BrowserRouter
,並且是從react-router-dom
引入,而不是react-router
接下來,咱們用肉眼就能看出不少的變化,首先,V3
中的 router
不在了,在 V3
中,咱們是將整個龐大的 router
直接丟給 DOM
,而在 V4
中,除了 BrowserRouter
, 咱們丟給 DOM
的是咱們的應用程序自己
另外,V4
中,咱們再也不使用 {props.children}
來嵌套組件了,替代的 <Route>
,當 route
匹配時,子組件會被渲染到 <Route>
書寫的地方
在上面的 example
中,讀者可能注意到 V4
中有 exact
這麼一個 props
,那麼,這個 props
有什麼用呢? V3
中的 routing
規則是 exclusive
,意思就是最終只獲取一個 route
,而 V4
中的 routes
默認是 inclusive
的,這就意味着多個 <Route>
能夠同時匹配
和呈現
仍是使用上面的 example
,若是咱們調皮地刪除 exact
這個 props
,那麼咱們在訪問 /user
的時候,Home
和 User
兩個 Page
都會被渲染,是否是一下就明白了
爲了更好地理解
V4
的匹配邏輯,能夠查看 path-to-regexp,就是它決定routes
是否匹配URL
爲了演示 inclusive routing
的做用,咱們新增一個 UserMenu
組件以下
const PrimaryLayout = () => <div className="primary-layout"> <header> Our React Router 4 App <Route path="/user" component={UsersMenu} /> </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/user" component={UsersPage} /> </main> </div>;
如今,當訪問 /user
時,兩個組價都會被渲染,在 V3
中存在一些模式也能夠實現,但過程實在是複雜,在 V4
中,是否是感受輕鬆了不少
若是你只想匹配一個 route
,那麼你也可使用 <Switch>
來 exclusive routing
const PrimaryLayout = () => <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/user/add" component={UserAddPage} /> <Route path="/user" component={UsersPage} /> <Redirect to="/" /> </Switch> </main> </div>;
在 <Switch>
中只有一個 <Route>
會被渲染,另外,咱們仍是要給 HomePage
所在 <Route>
添加 exact
,不然,在訪問 /user
或 /user/add
的時候仍是會匹配到 /
,從而,只渲染 HomePage
。同理,不知有沒同窗注意到,咱們將 /user/add
放在 /user
前面是保證正確匹配
的頗有策略性
的一步,由於,/user/add
會同時匹配 /user
和 /user/add
,若是不這麼作,你們能夠嘗試交換它們兩個的位置,看下會發生什麼
固然,若是咱們給每個 <Route>
都添加一個 exact
,那就不用考慮上面的 策略
了,但無論怎樣,如今至少知道了咱們還有其它選擇
<Redirect>
組件不用多說,執行瀏覽器重定向,但它在 <Switch>
中時,<Redirect>
組件只會在 routes
匹配不成功的狀況下渲染,另外,要想了解 <Redirect>
如何在 non-switch
環境下使用,能夠參考下面的 Authorized Route
V4
中也沒有 <IndexRoute>
,但 <Route exact>
能夠實現相同的功能,或者 <Switch>
和 <Redirect>
重定向到默認的有效路徑,甚至一個找不到的頁面
接下來,你可能很想知道 V4
中是如何實現 嵌套佈局
的,V4
確實給咱們了不少選擇,但這並不必定是好事,表面上,嵌套佈局
微不足道,但選擇的空間越大,出現的問題也就可能越多
如今,咱們假設咱們要增長兩個 user
相關的頁面,一個 browse user
,一個 user profile
,對 product
咱們也有相同的需求,實現的方法可能並很多,但有的仔細思考後可能並不想採納
第一種,以下修改 PrimaryLayout
const PrimaryLayout = props => { return ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/user" exact component={BrowseUsersPage} /> <Route path="/user/:userId" component={UserProfilePage} /> <Route path="/products" exact component={BrowseProductsPage} /> <Route path="/products/:productId" component={ProductProfilePage} /> <Redirect to="/" /> </Switch> </main> </div> ); };
雖然這種方法能夠實現,但仔細觀察下面的兩個 user
頁面,就會發現有點潛在的 問題
const BrowseUsersPage = () => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <BrowseUserTable /> </div> </div> ) const UserProfilePage = props => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <UserProfile userId={props.match.params.userId} /> </div> </div> )
userId
經過props.match.params
獲取,props.match
賦予給了<Route>
中的任何組件。除此以外,若是組件不經過<Route>
來渲染,要訪問props.match
,可使用withRouter()
高階組件來實現
估計你們都發現了吧,兩個 user
頁面中都有一個<UserNav />
,這明顯會致使沒必要要的請求
,以上只是一個簡單實例,若是是在真實的項目中,不知道會重複消耗多少的流量,然而,這就是由咱們以上方式使用路由引發的
接下來,咱們再看看另外一種實現方式
const PrimaryLayout = props => { return ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/user" component={UserSubLayout} /> <Route path="/products" component={ProductSubLayout} /> <Redirect to="/" /> </Switch> </main> </div> ); };
咱們用 2 個 routes
替換以前的 4 個 routes
注意,這裏咱們沒有再使用
exact
,由於,咱們但願/user
能夠匹配任何以/user
開始的route
,products
同理
使用這種策略,子佈局也開始承擔起了渲染 routes
的責任,如今,UserSubLayout
長這樣
const UserSubLayout = () => <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path="/user" exact component={BrowseUsersPage} /> <Route path="/user/:userId" component={UserProfilePage} /> </Switch> </div> </div>;
如今是否是解決了第一種方式中的生命週期,重複渲染
的問題呢?
但有一點值得注意的是,routes
須要識別它的完整路徑才能匹配,爲了減小咱們的重複輸入,咱們可使用 props.match.path
來代替
const UserSubLayout = props => <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path={props.match.path} exact component={BrowseUsersPage} /> <Route path={`${props.match.path}/:userId`} component={UserProfilePage} /> </Switch> </div> </div>;
正如咱們上面看到的那樣,props.match
能夠幫咱們獲取 userId
和 routes
match
對象爲咱們提供了 match.params
,match.path
,和 match.url
等屬性
最開始,可能以爲這二者的區別並不明顯,控制檯常常出現相同的輸出,好比,訪問 /user
const UserSubLayout = ({ match }) => { console.log(match.url) // output: "/user" console.log(match.path) // output: "/user" return ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path={match.path} exact component={BrowseUsersPage} /> <Route path={`${match.path}/:userId`} component={UserProfilePage} /> </Switch> </div> </div> ) }
match
在組件的參數中被解構,意思就是咱們可使用match.path
代替props.match.path
雖然咱們看不到什麼明顯的差別,但須要明白的是 match.url
是瀏覽器 URL
的一部分,match.path
是咱們爲 router
書寫的路徑
若是咱們是構建 route
路徑,那麼確定使用 match.path
爲了說明問題,咱們建立兩個子組件,一個 route
路徑來自 match.url
,一個 route
路徑來自 match.path
const UserComments = ({ match }) => <div> UserId: {match.params.userId} </div>; const UserSettings = ({ match }) => <div> UserId: {match.params.userId} </div>; const UserProfilePage = ({ match }) => <div> User Profile: <Route path={`${match.url}/comments`} component={UserComments} /> <Route path={`${match.path}/settings`} component={UserSettings} /> </div>;
而後,咱們按下面方式來訪問
/user/5/comments
/user/5/settings
實踐後,咱們發現,訪問 comments
返回 undefined
,訪問 settings
返回 5
正如 API
所述
match
:path
- (string) The path pattern used to match. Useful for building nested <Route>surl
- (string) The matched portion of the URL. Useful for building nested <Link>s
假設咱們的 App
是一個儀表盤,咱們但願訪問 /user/add
和 /user/5/edit
添加和編輯 user
。使用上面的實例,user/:userId
已經指向 UserProfilePage
,咱們這是須要在 UserProfilePage
中再添加一層 routes
麼?顯示不是這樣的
const UserSubLayou = ({ match }) => <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route exact path={match.path} component={BrowseUsersPage} /> <Route path={`${match.path}/add`} component={AddUserPage} /> <Route path={`${match.path}/:userId/edit`} component={EditUserPage} /> <Route path={`${match.path}/:userId`} component={UserProfilePage} /> </Switch> </div> </div>;
如今,看清楚這個策略
了麼
另外,咱們使用 ${match.path}/:userId(\\d+)
做爲 UserProfilePage
對應的 path
,保證 :userId
是一個數字,能夠避免與 /users/add
的衝突,這樣,將其所在的 <Route>
丟到最前面去也能正常訪問 add
頁面,這一招,就是我在 path-to-regexp 學的
在應用程序中限制未登陸的用戶訪問某些路由
是很是常見的,還有對於受權
和未受權
的用戶 UI
也可能大不同,爲了解決這樣的需求,咱們能夠考慮爲應用程序設置一個主入口
class App extends React.Component { render() { return ( <Provider store={store}> <BrowserRouter> <Switch> <Route path="/auth" component={UnauthorizedLayout} /> <AuthorizedRoute path="/app" component={PrimaryLayout} /> </Switch> </BrowserRouter> </Provider> ) } }
如今,咱們首先會去選擇應用程序在哪一個頂級佈局
中,好比,/auth/login
和 /auth/forgot-password
確定在 UnauthorizedLayout
中,另外,當用戶登錄時,咱們將判斷全部的路徑都有一個 /app
前綴以確保是否登陸。若是用戶訪問 /app
開頭的頁面但並無登陸,咱們將會重定向
到登陸頁面
下面就是我寫的 AuthorizedRoute
組件,這也是 V4
中一個驚奇的特性,能夠爲了知足某種須要而書寫本身的路由
class AuthorizedRoute extends React.Component { componentWillMount() { getLoggedUser(); } render() { const { component: Component, pending, logged, ...rest } = this.props; return ( <Route {...rest} render={props => { if (pending) return <div>Loading...</div>; return logged ? <Component {...this.props} /> : <Redirect to="/auth/login" />; }} /> ); } } const stateToProps = ({ loggedUserState }) => ({ pending: loggedUserState.pending, logged: loggedUserState.logged }); export default connect(stateToProps)(AuthorizedRoute);
點擊 這裏 能夠查看的個人整個 Authentication
React Router 4
相比 V3
,變化很大,如果以前的項目使用的 V3
,不建議當即升級,但 V4
比 V3
確實存在較大的優點