在React Router 3上寫了一篇文章後不久,我第一次在React Rally 2016上遇到了Michael Jackson。Michael是React Router和Ryan Florence的主要做者之一。遇到一個建造了我很是喜歡的工具的人真是使人興奮,但當他說時我很震驚。「讓我告訴你咱們的想法React Router 4,它的方式不同!「說實話,我不明白新方向以及爲何須要這麼大的改變。因爲路由器是應用程序架構的重要組成部分,這可能會改變我已經愛上的一些模式。這些變化讓我感到焦慮。考慮到社區凝聚力以及React Router在如此衆多的React應用程序中扮演重要角色,我不知道社區將如何接受這些變化。javascript
幾個月以後,React Router 4發佈了,我能夠從Twitter的嗡嗡聲中看出,在激烈的重寫中有一種複雜的感受。它讓我想起了初版React Router對其漸進概念的推回。在某些方面,早期版本的React Router經過將全部路由規則放在一個地方,相似於咱們應用路由器「應該」的傳統心理模型。可是,並非每一個人都接受使用嵌套的JSX路由。但正如JSX自己克服了批評者(至少大多數人),許多人相信嵌套的JSX路由器是一個很是酷的主意。css
因此,我學習了React Router 4.不能否認,這是第一天的一場鬥爭。鬥爭不是API,而是使用它的模式和策略。我使用React Router 3的心理模型不能很好地遷移到v4。若是我要成功的話,我將不得不改變我對路由器和佈局組件之間關係的見解。最終,出現了對我有意義的新模式,我對路由器的新方向感到很是滿意。React Router 4容許我作我能夠用v3作的一切,等等。並且,起初,我過分複雜化了v4的使用。一旦我得到了一個新的心智模型,我意識到這個新的方向是驚人的!html
我對本文的意圖不是要從新編寫已經編寫好的 React Router 4 文檔。我將介紹最多見的API概念,但真正關注的是我發現成功的模式和策略。java
如下是本文須要熟悉的一些JavaScript概念:react
若是您喜歡跳到正常工做演示的類型,請轉到:git
查看演示github
早期版本的React Router將路由規則集中到一個位置,使它們與佈局組件分開。固然,路由器能夠被分區並組織成幾個文件,但從概念上講,路由器是一個單元,基本上是一個美化的配置文件。web
也許瞭解v4如何不同凡響的最好方法是在每一個版本中編寫一個簡單的兩頁應用程序並進行比較。示例應用程序只有兩個主頁和用戶頁面的路由。npm
這是在第3節:redux
import { Router, Route, IndexRoute } from 'react-router' const PrimaryLayout = props => ( <div className="primary-layout"> <header> Our React Router 3 App </header> <main> {props.children} </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <Router history={browserHistory}> <Route path="/" component={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/users" component={UsersPage} /> </Route> </Router> ) render(<App />, document.getElementById('root'))
如下是v3中的一些關鍵概念,這些概念在v4中再也不適用:
<Route>
組件派生的。React Router 4再也不提倡集中式路由器。相反,路由規則存在於佈局內以及UI自己之間。例如,這是v4中的相同應用程序:
import { BrowserRouter, Route } from 'react-router-dom' const PrimaryLayout = () => ( <div className="primary-layout"> <header> Our React Router 4 App </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <BrowserRouter> <PrimaryLayout /> </BrowserRouter> ) render(<App />, document.getElementById('root'))
新API概念:因爲咱們的應用程序適用於瀏覽器,咱們須要將其包裝在<BrowserRouter>
v4中。另請注意咱們從react-router-dom
如今開始導入(這意味着咱們npm install react-router-dom
沒有react-router
)。暗示!它react-router-dom
如今被稱爲由於還有一個原生版本。
在查看使用React Router v4構建的應用程序時,第一件事就是「路由器」彷佛缺失了。在v3中,路由器是咱們直接向DOM編寫的一個巨大的東西,它編排了咱們的應用程序。如今,除此以外<BrowserRouter>
,咱們投入DOM的第一件事就是咱們的應用程序自己。
v4示例中缺乏的另外一個v3-staple是使用 {props.children}
嵌套組件。這是由於在v4中,不管<Route>
編寫組件的哪一個位置,若是路徑匹配,子組件將呈現給的位置。
在前面的例子中,您可能已經注意到exact
道具。那一切都是關於什麼的?V3路由規則是「獨佔的」,這意味着只有一條路線會贏。默認狀況下,V4路由是「包含」的,這意味着多個路由<Route>
能夠同時匹配和呈現。
在前面的示例中,咱們嘗試渲染路徑HomePage
或UsersPage
依賴路徑。若是exact
從示例中刪除了prop,則在瀏覽器中訪問`/ users`時,它們 HomePage
和UsersPage
組件都會同時呈現。
要更好地理解匹配邏輯,請查看path-to-regexp,這是v4如今用於肯定路由是否與URL匹配的內容。
爲了演示包容性路由是如何有用的,讓咱們UserMenu
在標題中包含一個,但前提是咱們在應用程序的用戶中:
const PrimaryLayout = () => ( <div className="primary-layout"> <header> Our React Router 4 App <Route path="/users" component={UsersMenu} /> </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> )
如今,當用戶訪問`/ users`時,兩個組件都將呈現。這樣的東西在某些模式的v3中是可行的,但它更難。感謝v4的包容性路線,如今變得垂手可得。
若是您只須要在一個組中匹配一條路由,請使用<Switch>
以啓用獨佔路由:
const PrimaryLayout = () => ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/users/add" component={UserAddPage} /> <Route path="/users" component={UsersPage} /> <Redirect to="/" /> </Switch> </main> </div> )
只有給定的一條路線<Switch>
會渲染。若是咱們要首先列出它,咱們仍然須要exact
在HomePage
路線上。不然,當訪問諸如`/ users`或`/ users / add`之類的路徑時,主頁路由將匹配。實際上,戰略佈局是使用獨佔路由策略時的遊戲名稱(由於它一直與傳統路由器同樣)。請注意,咱們策略性地放置/users/add
以前的路線 /users
以確保正確匹配。因爲路徑/users/add
將匹配`/ users`和`/ users / add`,所以放第/users/add
一個是最好的。
固然,若是咱們exact
以某種方式使用它們,咱們能夠將它們置於任何順序,但至少咱們有選擇權。
該<Redirect>
組件將永遠作一個瀏覽器重定向若是遇到過,可是當它在一個 <Switch>
聲明中,重定向組件只獲取呈如今沒有其餘途徑首先匹配。要了解如何<Redirect>
在非切換環境中使用,請參閱下面的受權路徑。
雖然<IndexRoute>
在v4中沒有更多,但使用<Route exact>
一樣的東西。或者,若是沒有解析路由,則使用 <Switch>
with <Redirect>
重定向到具備有效路徑的默認頁面(如我HomePage
在示例中所作的那樣),甚至是未找到的頁面。
您可能已經開始預期嵌套的子佈局以及如何實現它們。我不認爲我會掙扎這個概念,但我作到了。React Router v4爲咱們提供了不少選項,這使它變得強大。可是,選項意味着能夠自由選擇不理想的策略。從表面上看,嵌套佈局是微不足道的,但根據您的選擇,您可能會由於組織路由器的方式而遇到摩擦。
爲了演示,讓咱們假設咱們想要擴展咱們的用戶部分,以便咱們有一個「瀏覽用戶」頁面和一個「用戶我的資料」頁面。咱們還想要相似的產品頁面。用戶和產品都須要對每一個相應部分都特殊且獨特的子佈局。例如,每一個可能有不一樣的導航選項卡。有幾種方法能夠解決這個問題,一些是好的,一些是壞的。第一種方法不是很好,但我想告訴你,因此你不要陷入這個陷阱。第二種方法要好得多。
首先,讓咱們修改咱們PrimaryLayout
以適應用戶和產品的瀏覽和我的資料頁面:
const PrimaryLayout = props => { return ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/users" exact component={BrowseUsersPage} /> <Route path="/users/:userId" component={UserProfilePage} /> <Route path="/products" exact component={BrowseProductsPage} /> <Route path="/products/:productId" component={ProductProfilePage} /> <Redirect to="/" /> </Switch> </main> </div> ) }
雖然這在技術上有效,但仔細查看兩個用戶頁面就會開始顯示問題:
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> )
新的API概念: props.match
給予呈現的任何組件<Route>
。如您所見,由userId
提供props.match.params
。在v4文檔中查看更多內容。或者,若是任何組件須要訪問props.match
但組件未<Route>
直接呈現,咱們可使用withRouter()高階組件。
每一個用戶頁面不只呈現其各自的內容,並且還必須關注子佈局自己(而且對於每一個用戶頁面重複子佈局)。雖然這個例子很小而且看似微不足道,但重複的代碼在實際應用中多是個問題。更不用說,每次渲染BrowseUsersPage
或UserProfilePage
渲染時,它都會建立一個新實例,UserNav
這意味着它的全部生命週期方法都會從新開始。若是導航選項卡須要初始網絡流量,這將致使沒必要要的請求 - 全部這些都是由於咱們決定使用路由器。
這是一種更好的不一樣方法:
const PrimaryLayout = props => { return ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UserSubLayout} /> <Route path="/products" component={ProductSubLayout} /> <Redirect to="/" /> </Switch> </main> </div> ) }
而不是對應於每一個用戶和產品頁面的四條路線,而是爲每一個部分的佈局設置兩條路線。
請注意,上述路線再也不使用exact
道具,由於咱們但願/users
匹配任何/users
以產品開頭且相似的路線。
使用此策略,子佈局的任務是呈現其餘路徑。這是UserSubLayout
可能的樣子:
const UserSubLayout = () => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path="/users" exact component={BrowseUsersPage} /> <Route path="/users/:userId" component={UserProfilePage} /> </Switch> </div> </div> )
新策略中最明顯的勝利是全部用戶頁面之間不會重複佈局。這也是共贏,由於它不會像第一個例子那樣具備相同的生命週期問題。
有一點須要注意的是,即便咱們深深嵌套在佈局結構中,路由仍然須要識別它們的完整路徑才能匹配。爲了節省本身的重複輸入(若是您決定將「用戶」更改成其餘內容),請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
輪廓的渲染以及編寫路線很是有用。該match
對象爲咱們提供了一些特性,包括match.params
,match.path
,match.url
和幾個。
這二者之間的差別一開始彷佛不清楚。記錄它們的控制檯有時能夠顯示相同的輸出,使得它們的差別更加明顯。例如,當瀏覽器路徑爲「/ users」時,這兩個控制檯日誌都將輸出相同的值:
const UserSubLayout = ({ match }) => { console.log(match.url) // output: "/users" console.log(match.path) // output: "/users" 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> ) }
ES2015概念: match
正在組件功能的參數級別進行解構。這意味着咱們能夠輸入match.path
而不是props.match.path
。
雖然咱們看不到差別,可是match.url
瀏覽器URL中的實際路徑match.path
是爲路由器編寫的路徑。這就是爲何它們是相同的,至少到目前爲止。可是,若是咱們作了一樣的控制檯日誌一個更深層次的UserProfilePage
參觀`/用戶/ 5`在瀏覽器中,match.url
將"/users/5"
和match.path
會"/users/:userId"
。
若是你打算使用其中一個來幫助創建你的路線,我建議你選擇match.path
。使用match.url
創建路由路徑,最終會致使您不想要的場景。這是一個發生在我身上的情景。在組件內部UserProfilePage
(當用戶訪問`/ users / 5`時呈現),我渲染了以下的子組件:
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> )
爲了說明這個問題,我正在渲染兩個子組件,其中一個路徑路徑來自match.url
,另外一個來自match.path
。如下是在瀏覽器中訪問這些頁面時發生的狀況:
那麼爲何match.path
要幫助創建咱們的道路match.url
呢?答案在於,這{${match.url}/comments}
與我硬編碼基本相同{'/users/5/comments'}
。這樣作意味着後續組件將沒法match.params
正確填充,由於路徑中沒有參數,只有硬編碼5
。
直到後來我纔看到這部分文檔,並意識到它的重要性:
比賽:
- path - (字符串)用於匹配的路徑模式。用於構建嵌套的
<Route>
s- url - (字符串)URL的匹配部分。用於構建嵌套的
<Link>
s
讓咱們假設咱們正在製做的應用程序是一個儀表板,所以咱們但願可以經過訪問`/ users / add`和`/ users / 5 / edit`來添加和編輯用戶。可是經過前面的例子,users/:userId
已經指出了一個UserProfilePage
。那麼這是否意味着如今的路線users/:userId
須要指向另外一個子子佈局以適應編輯和配置文件?我不這麼認爲。因爲編輯頁面和配置文件頁面共享相同的用戶子佈局,所以該策略能夠正常工做:
const UserSubLayout = ({ match }) => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route exact path={props.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> )
請注意,添加和編輯路由策略性地位於配置文件路由以前,以確保正確匹配。若是配置文件路徑是第一個,訪問`/ users / add`將匹配配置文件(由於「添加」將匹配:userId
。
或者,若是咱們創建${match.path}/:userId(\\d+)
確保:userId
必須是數字的路徑,咱們能夠將配置文件路由放在第一位。而後訪問`/ users / add`不會產生衝突。我在docs for path-to-regexp中學到了這個技巧。
在應用程序中很是常見的是限制用戶根據其登陸狀態訪問某些路由的能力。一樣常見的是對未受權頁面(如「登陸」和「忘記密碼」)的「外觀」與受權用戶的「外觀」(應用程序的主要部分) 。要解決這些需求,請考慮應用程序的這個主要入口點:
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> ) } }
使用反應,終極版的做品很是類似與之反應路由器V4像之前同樣,簡單地包裹<BrowserRouter>
在<Provider>
和它的全部設置。
這種方法有一些要點。第一個是我在兩個頂級佈局之間進行選擇,具體取決於咱們所在的應用程序部分。訪問路徑如`/ auth / login`或`/ auth / forgot-password`將使用UnauthorizedLayout
-看起來適合這些狀況。當用戶登陸時,咱們將確保全部路徑都有一個`/ app`前綴,用於AuthorizedRoute
肯定用戶是否已登陸。若是用戶嘗試訪問以「/ app」開頭且未登陸的頁面,則會將其重定向到登陸頁面。
AuthorizedRoute
雖然不是v4的一部分。我是在v4 docs的幫助下本身作的。v4中的一個驚人的新功能是可以爲特定目的建立本身的路線。而不是傳遞component
道具<Route>
,render
而是傳遞回調:
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)
雖然您的登陸策略可能與我不一樣,我用一個網絡請求getLoggedUser()
和插頭pending
和logged
爲Redux的狀態。pending
只是意味着請求仍然在路上。
React Router v4還有不少其餘很酷的方面。儘管如此,咱們必定要提一些小東西,這樣他們就不會讓你措手不及。
在v4中,有兩種方法能夠將錨標記與路由器集成:<Link>
和<NavLink>
<NavLink>
工做方式相同,<Link>
但根據是否<NavLink>
與瀏覽器的URL匹配,爲您提供一些額外的樣式功能。例如,在示例應用程序中,有一個以下所示的<PrimaryHeader>
組件:
const PrimaryHeader = () => ( <header className="primary-header"> <h1>Welcome to our app!</h1> <nav> <NavLink to="/app" exact activeClassName="active">Home</NavLink> <NavLink to="/app/users" activeClassName="active">Users</NavLink> <NavLink to="/app/products" activeClassName="active">Products</NavLink> </nav> </header> )
使用<NavLink>
容許我設置一個類active
到活動連接。可是,請注意我也可使用exact
這些。因爲exact
v4的包容性匹配策略,在訪問`/ app / users`時沒有主頁連接將是活動的。根據個人我的經驗,<NavLink>
選項exact
比v3 <Link>
至關穩定得多。
沒有辦法從React Router v4獲取URL的查詢字符串。在我看來,作出這個決定是由於沒有關於如何處理複雜查詢字符串的標準。所以,他們決定讓開發人員選擇如何處理查詢字符串,而不是v4對模塊發表意見。這是件好事。
就我的而言,我使用的查詢字符串是由老是很棒的sindresorhus製做的。
關於v4的最好的部分之一是幾乎全部東西(包括<Route>
)都只是一個React組件。路線再也不是神奇的東西了。咱們能夠隨時隨地呈現它們。想象一下,當知足某些條件時,您的應用程序的整個部分可用於路由。若是不知足這些條件,咱們能夠刪除路線。咱們甚至能夠作一些瘋狂的酷遞歸路線。
React Router 4更容易,由於它是Just Components™
1 import React, {Component} from 'react'; 2 import {BrowserRouter as Router, Route, Link, Switch, Redirect,NavLink,withRouter} from 'react-router-dom' 3 import logo from './logo.svg'; 4 import './App.css'; 5 6 const Header = () => ( 7 <ul> 8 <li> 9 <NavLink to="/" exact activeClassName="active">Home</NavLink> 10 </li> 11 <li> 12 <NavLink to="/users" activeClassName="active">User</NavLink> 13 </li> 14 <li> 15 <NavLink to="/products" activeClassName="active">products</NavLink> 16 </li> 17 {/*<li>*/} 18 {/*<Link to="/topics">Topics</Link>*/} 19 {/*</li>*/} 20 </ul> 21 22 ); 23 const HomePage = () => <h2>Home</h2>; 24 const UserNav = ({match}) => { 25 return ( 26 <div> 27 <NavLink to={`${match.path}`} exact activeClassName="active">Browse</NavLink> 28 <NavLink to={`${match.path}/add`} activeClassName="active">Add</NavLink> 29 </div> 30 31 )} 32 33 const UserProfilePage = ({ match }) => ( 34 <div> 35 User Profile for user: {match.params.userId} 36 </div> 37 ) 38 39 const AddUserPage = ({ match }) => ( 40 <div> 41 Add Users 42 </div> 43 ) 44 45 const BrowseUsersPage = ({ match }) => ( 46 <div> 47 Browse Users 48 <ul> 49 <li><Link to={`${match.path}/1`}>Brad</Link></li> 50 <li><Link to={`${match.path}/2`}>Chris</Link></li> 51 <li><Link to={`${match.path}/3`}>Michael</Link></li> 52 <li><Link to={`${match.path}/4`}>Ryan</Link></li> 53 </ul> 54 </div> 55 ) 56 57 const PrimaryLayout = ({match}) => { 58 return ( 59 <div className="primary-layout"> 60 <Header/> 61 <main> 62 {/*在沒有switch的狀況下,若是不寫exact,也沒有redirect的時候,若是訪問/users/add的路徑的時候,那麼UsersAddPage和UsersPage頁面都會渲染,若是遇到 63 redirect,無論有沒有exact,那麼就會一直重定向到默認的路徑,router4已經沒有IndexRoute了,能夠採用<Route exact>作到,Switch表示只匹配到第一條*/} 64 <Switch> 65 <Route path='/' exact component={HomePage}/> 66 <Route path='/users' component={UserSubLayout} /> 67 <Route path='/products' component={ProductSubLayout} /> 68 </Switch> 69 </main> 70 </div> 71 ) 72 } 73 74 const UserSubLayout = ({match}) => ( 75 <div className="user-sub-layout"> 76 <aside> 77 <UserNav match={match}/> 78 </aside> 79 <div className="primary-content"> 80 <Switch> 81 <Route path={match.path} exact component={BrowseUsersPage}/> 82 <Route path={`${match.path}/add`} exact component={AddUserPage} /> 83 <Route path={`${match.path}/:userId`} exact component={UserProfilePage}/> 84 </Switch> 85 86 </div> 87 </div> 88 ) 89 90 const ProductSubLayout = ({ match }) => ( 91 <div className="product-sub-layout"> 92 I didn't take the time to flesh out the product sub layout because it would have been 93 just like the user sub layout. The point is that we can have sub layouts this way. 94 </div> 95 ) 96 97 98 class App extends Component { 99 render() { 100 return ( 101 <PrimaryLayout/> 102 ) 103 } 104 } 105 106 export default App;
https://www.cnblogs.com/zhanghuiming/p/7592132.html react-router4文檔
https://reacttraining.com/react-router/web/guides/quick-start-------官方文檔