react-router @4用法整理

在React Router 3上了一篇文章后不久,第一次在React Rally 2016上遇到了Michael JacksonMichael是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

一种新的API和一种新的心理模型

早期版本的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>能够同时匹配和呈现。

在前面的示例中,咱们尝试渲染路径HomePageUsersPage依赖路径。若是exact从示例中删除prop,则在浏览器中访问`/ users`时,它们 HomePageUsersPage组件都会同时呈现。

要更好地理解匹配逻辑,请查看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>会渲染。若是咱们要首先列出它,咱们仍然须要exactHomePage路线不然,当访问诸如`/ 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.paramsv4文档中查看更多内容或者,若是任何组件须要访问props.match但组件未<Route>直接呈现,咱们可使用withRouter()高阶组件。

每一个用户页面不只呈现其各自的内容,并且还必须关注子布局自己(而且对于每一个用户页面重复子布局)。虽然这个例子很小而且看似微不足道,但重复的代码在实际应用中多是个问题。更不用说,每次渲染BrowseUsersPageUserProfilePage渲染时,它都会建立一个新实例,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.paramsmatch.pathmatch.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()和插头pendinglogged为Redux的状态。pending只是意味着请求仍然在路上。

单击此处以查看CodePen上彻底正常运行的身份验证示例

其余说起

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这些。因为exactv4的包容性匹配策略,在访问`/ 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-------官方文档

相关文章
相关标签/搜索