(译)React-Router4的变化

栏目: IOS · Android · 发布时间: 5年前

内容简介:首先,这篇文章的目的并不是为了重新叙述一遍React-Router4的文档。接下来我要说的内容,将会覆盖React-Router的大多数API,但是真正的目的是揭开React-Router4成功的模式和策略。在开始本文之前,你需要了解一些JS的概念:如果你喜欢直接看demo来了解,请点击此链接查看。
  • 原文地址:戳这里
  • 项目地址:传送门

首先,这篇文章的目的并不是为了重新叙述一遍React-Router4的文档。接下来我要说的内容,将会覆盖React-Router的大多数API,但是真正的目的是揭开React-Router4成功的模式和策略。

在开始本文之前,你需要了解一些JS的概念:

  • React无状态函数式组件
  • ES6箭头函数以及它的“隐式返回”
  • ES6解构赋值
  • ES6模板字符串

如果你喜欢直接看demo来了解,请点击此链接查看。

新的API以及新的心智模型

React-Router早一些的版本中,是在一个地方统一地管理所有路由规则,将它们与布局组件分离。当然,路由也可以被拆分和组织到几个文件当中,但是从概念上来说,路由本身是一个单元,基本上是一个美化的配置文件。

要了解版本4到底有哪些不同,最好的方式可能是用每一个版本写一个两个页面的应用,并进行比较。示例的应用只有home页面和user页面。

以下是版本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'))
复制代码

版本3中的一些概念在版本4中可能不再适用:

<Route>

React-Router4不在提倡集中管理路由。相反,路由规则存在于布局和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概念:由于我们的应用是针对浏览器的,因此我们需要将它包装在V4提供的 <BrowserRouter> 组件中。我们也注意到,使用路由时我们引入的是 react-router-dom ,这意味着你在开发时应该安装 react-router-dom 而不是 react-router 。这里有个小小的暗示,既然它的名字是 react-router-dom ,这意味着它也提供了原生版本。

在使用V4版本构建的应用中,最显著的一点是“路由”似乎消失了。在V3中,路由是我们渲染到dom的一个大玩意儿,它统筹了我们的整个项目。现在,除了 <BrowserRouter> 以外,第一个抛进dom的是我们的应用本身。

另一个在V4示例中没有展现的V3的用法是使用props.children来嵌套组件。这是因为在V4中,只要路由匹配到了,<Router>组件在哪里编写,子组件就会渲染在哪里。

包含式路由

在先前的例子中,你可能已经注意到了 exact 属性。那么它到底是干嘛的呢?在V3中,路由规则是独一无二的,这意味着最终只有一个路由会匹配到。但是在V4中,路由是包含式的,这意味着可能会有多个路由同时匹配到并且渲染。

在先前的例子中,我们试图依靠路径来区分渲染home页面或者user页面。如果把 exact 属性从该示例中移除,那么在浏览‘/user’路径时,home页面和user页面会同时渲染。

想要更好的了解匹配规则,您可以查看path-to-regexp,React-Router4 就是使用它来匹配路由的。

为了更好地演示包含式路由起到了什么作用,我们实现一个这样的功能:只有在user页面我们才在头部添加一个 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> 组件来实现。 <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> 中的路由只有一个会被渲染。我们仍然需要在home页面的路由上添加exact属性,否则,当我们访问‘/users’或者‘/users/add’时,home页面将会首先匹配到。事实上,在使用排他性路由时,路由的位置排列是最为关键的事,传统路由一直如此。我们把‘/users/add’排列在‘/users’之前以保证正确的匹配路由。因为‘/users/add’会匹配‘/users’和‘/users/add’,把‘/users/add’放在前面是最好的。

当然,如果你使用了 exact 属性,那放在哪儿都无所谓,但至少我们还有选择。

在遇到 <Redirect> 组件时,总是会做浏览器重定向,但是如果它在 <Switch> 组件中,那么只有当其他路由都没有匹配到时,才会重定向。

“Index Routes” 和 “Not Found”

在V4中我们用 exact 属性代替了 <IndexRoute> 来做同样的事。在没有路由匹配到时,使用 <Redirect> 来重定向到默认页面的路径,或者404页面。

嵌套布局

现在你可能已经开始思考如何实现嵌套布局了。我原本以为我不会纠结于这个概念,但我没有...V4提供了很多选择,这就是为什么V4如此强大。但是,多样的选择意味着我们可能选择并不理想的方案。表面上看来,嵌套布局很简单,但是根据你的选择不同,你可能会遇到一些麻烦因为你组织路由的方式。

想象这样一个场景,我们需要一个‘浏览用户’的页面以及‘用户信息’的页面。对于产品页面我们也有类似需求。用户和产品都需要一个子布局,并且子布局都是唯一特有的。举例来说,每一个子布局拥有不同的导航卡。我们有几种不同的方式来解决这个问题,有些比较好有些则不太好。第一种方案可能并不是那么好,但我还是想让你们看一看,以免以后踩入这个坑。第二种方案就显得更好一些。

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概念:任何由 <Route> 渲染的组件,V4都会提供 props.match 。就像你看到的,userId这个参数是有 props.match.params 提供的。了解更多V4文档。如果一个组件并不是直接通过 <Route> 组件渲染的,但是却要用到 props.match ,那么我们可以借助 withRouter() 这个高阶组件。

实现上来说,第一种方案并没有什么问题。但是我们仔细观察一下,会发现 UserProfilePageBrowserUsersPage 的布局是一样的,不同的只是 UserProfileBrowserUserTable 。这种实现方法会导致一些冗余代码,并且在 UserProfilePageBrowserUsersPage 之间切换时, 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>
  )
}

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>
)
复制代码

和之前实现方式不同的是,我们的路由从4个变成了2个。 BrowseUsersPageUserProfilePage 组件只负责渲染不同的部分,作为 UserSubLayout 的子组件。需要注意的一点是,不论你路由嵌套多深,还是需要写完整路径去匹配。如果你不想重复书写路径或者方便统一修改路径,你可以使用 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>
)
复制代码

Match

正如我们所见,props.match在获取userId这类参数和简化路由书写方面很有用处。match对象提供了一些属性: match.paramsmatch.pathmatch.url 等等

match.path 和 match.url

第一眼我们并不能清楚的区分这两者。控制台打印时,多数情况下也是相同的结果。举例来说,当浏览器路径是‘/users’时, match.pathmatch.url 打印输出的是相同的值。

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>
  )
}
复制代码

现在我们还是分不清这两者的区别,但是,如果我们用嵌套的路由,特别是带有参数的匹配模式,在更深一层的组件中打印,我们就能看到区别。比如我们浏览的是‘/users/5’这个路径, match.url 返回的是‘/users/5’,而 match.path 返回的是‘/users/:userId’。这样就一目了然了, match.url 返回的是具体的路由地址字符串,而 match.path 返回的是路由匹配模式的字符串。

如何选择?

在用 <Route> 做嵌套路由时,建议使用 match.path ,因为你可能在你的子组件中需要使用 match.param 对象,在 <Link> 组件做路由跳转时,使用 match.url

避免match冲突

假设现在我们需要一个编辑页面对用户信息进行编辑,那此页面的路由规则应该类似于‘/users/:userId/edit’。那么问题来了,前面的例子中,用户信息页面的路由匹配规则是 /users/:userId ,当我们访问‘/users/5/edit’的时候,将会首先匹配到用户信息页面而不是编辑页。那是否意味着我们要像之前做的那样,在原有的信息页面同时添加一个编辑的子页面,再去匹配呢?并不一定。我们可以通过将‘/users/:userId/edit’规则放在 /users/:userId 之前来达到此效果。或者对 /users/:userId 进行进一步约定,比如限制:userId为数字: users/:userId(\\d+)

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>
)
复制代码

授权路由

在我们的项目中,根据用户的登录状态来限制用户访问某些路由的功能是很常见的。让授权页面(应用程序的主要页面)和未授权页面(比如登录页面以及忘记密码页面)拥有不同的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/forget-password”将使用未经授权的布局。当用户登录时,我们将保证所有路径都有一个' /app '前缀,它使用 AuthorizedRoute 组件来确定用户是否登录。如果用户试图访问以' /app '开头的页面,但他们没有登录,他们将被重定向到登录页面。

但是 AuthorizedRoute 并不是V4本身提供的,而是我自己实现的。V4中一个惊人的新特性是能够为特定目的创建自己的路由。不同于通过传递一个组件属性到 <Route> ,而是传递一个 rendr 回调:

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-Router4的变化

其他注意点

React-Router V4还有其他更多炫酷的特性。最后,我们来了解一些小特性,以防遇到时犯迷糊。

<Link><NavLink>

在V4中,有两种方式可以将锚点和路由集成: <Link><NavLink>

<NavLink> 的原理和 <Link> 一样,不同的是, <NavLink> 能根据浏览器URL是否匹配从而提供额外的样式定制。详情可以在线查看该demo。部分代码如下:

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> 匹配到当前URL时,它允许我们在此 <NavLink> 上添加一个自定义的class,以便控制样式。

URL查询字符串

在V4中已经没有方式可以直接得到URL的查询字符串了。在我看来,做出这个决定是因为对于如何处理复杂的查询字符串没有标准。因此,v4没有在模块中引入相关方法,而是决定让开发人员选择如何处理查询字符串。这是一件好事。

就我个人而言,我使用sindresorhus大神(twitter)编写的query-string模块来处理查询字符串。

动态路由

V4最出色的一点是几乎所有的东西都是一个 React 组件,包括 <Route> 。路由再也不是什么魔法,我们可以在任何需要的时候有条件的渲染它们。想象一下,当满足某些条件时,您的应用程序的整个部分都可用于路由。当这些条件不满足时,我们可以删除路由。我们甚至可以做一些炫酷的事,比如递归路由。

React-Router4 变得更为简单了,因为万物皆组件。


以上所述就是小编给大家介绍的《(译)React-Router4的变化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++程序设计语言

C++程序设计语言

Bjarne Stroustrup / 裘宗燕 / 机械工业出版社 / 2010-3-1 / 99.00元

本书是在C++语言和程序设计领域具有深远影响、畅销不衰的著作,由C++语言的设计者编写,对C++语言进行了最全面、最权威的论述,覆盖标准C++以及由C++所支持的关键性编程技术和设计技术。本书英文原版一经面世,即引起业内人士的高度评价和热烈欢迎,先后被翻译成德、希、匈、西、荷、法、日、俄、中、韩等近20种语言,数以百万计的程序员从中获益,是无可取代的C++经典力作。 在本书英文原版面世10年......一起来看看 《C++程序设计语言》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具