在React Router 3上寫了一篇文章后不久,我第一次在React Rally 2016上遇到了Michael Jackson。Michael是React Router和Ryan Florence的主要作者之一。遇到一個建造了我非常喜歡的工具的人真是令人興奮,但當他說時我很震驚。“讓我告訴你我們的想法React Router 4,它的方式不一樣!“說實話,我不明白新方向以及為什么需要這么大的改變。由於路由器是應用程序架構的重要組成部分,這可能會改變我已經愛上的一些模式。這些變化讓我感到焦慮。考慮到社區凝聚力以及React Router在如此眾多的React應用程序中扮演重要角色,我不知道社區將如何接受這些變化。
幾個月之后,React Router 4發布了,我可以從Twitter的嗡嗡聲中看出,在激烈的重寫中有一種復雜的感覺。它讓我想起了第一版React Router對其漸進概念的推回。在某些方面,早期版本的React Router通過將所有路由規則放在一個地方,類似於我們應用路由器“應該”的傳統心理模型。但是,並不是每個人都接受使用嵌套的JSX路由。但正如JSX本身克服了批評者(至少大多數人),許多人相信嵌套的JSX路由器是一個非常酷的主意。
所以,我學習了React Router 4.不可否認,這是第一天的一場斗爭。斗爭不是API,而是使用它的模式和策略。我使用React Router 3的心理模型不能很好地遷移到v4。如果我要成功的話,我將不得不改變我對路由器和布局組件之間關系的看法。最終,出現了對我有意義的新模式,我對路由器的新方向感到非常滿意。React Router 4允許我做我可以用v3做的一切,等等。而且,起初,我過度復雜化了v4的使用。一旦我獲得了一個新的心智模型,我意識到這個新的方向是驚人的!
我對本文的意圖不是要重新編寫已經編寫好的 React Router 4 文檔。我將介紹最常見的API概念,但真正關注的是我發現成功的模式和策略。
以下是本文需要熟悉的一些JavaScript概念:
如果您喜歡跳到正常工作演示的類型,請轉到:
#一種新的API和一種新的心理模型
早期版本的React Router將路由規則集中到一個位置,使它們與布局組件分開。當然,路由器可以被分區並組織成幾個文件,但從概念上講,路由器是一個單元,基本上是一個美化的配置文件。
也許了解v4如何與眾不同的最好方法是在每個版本中編寫一個簡單的兩頁應用程序並進行比較。示例應用程序只有兩個主頁和用戶頁面的路由。
這是在第3節:
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
和幾個。
#match.path vs 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
。以下是在瀏覽器中訪問這些頁面時發生的情況:
- 訪問`/ users / 5 / comments`會顯示“UserId:undefined”。
- 訪問`/ users / 5 / settings`會顯示“UserId:5”。
那么為什么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還有很多其他很酷的方面。盡管如此,我們一定要提一些小東西,這樣他們就不會讓你措手不及。
#<Link> vs <NavLink>
在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>
相當穩定得多。
#URL查詢字符串
沒有辦法從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-------官方文檔