在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-------官方文档