React Router 4 简介及其背后的路由哲学

9,425 阅读9分钟

本文翻译自:An Introduction to React Router v4 and its Philosophy Toward Routing

译文地址:github.com/rccoder/blo…

本文翻译自:An Introduction to React Router v4 and its Philosophy Toward Routing

React Router 4 引入了一种基于 component 的动态路由。

这篇文章中会讨论思考 React Router 背后的哲学,同时也会通过分析 React Router 文档中的示例代码来介绍一下它的语法。

更多 React Router 的介绍请戳 这里

不想看文字版本还可以点击 这里 看视频。

如果你这几年一直在密切关注着 React,你应该会注意到 React Router 已经经历了多个版本的迭代。今天的 v4 版本更是一个巨大的改变。

产生这些变化的原因都是非常自然的 —— 今天 React 的开发者相比于 React Router 刚产生的时候更有经验。在 2014 年的时候,人人都是新手。没有人会想到 component 的概念在不足一年的 React 中会有这么重要。

因为上面的原因,React Router 的第一个 commit 是这样的:

当时,React Router 的作者 Michael 和 Ryan 都有着 Ember 的开发经验。自然着,React Router 的第一个版本和 Ember 的路由有点相似 —— 都是静态着去建立路由,作为应用初始化的一部分。

这种路由的概念就和熟悉的 Express、Angular、Ember 的概念一致。甚至在 React Router 4 release 之前,使用的也是静态路由。

使用静态路由的时候往往会有这样的一段代码写在 routes.js 里面:

const routes = (
  <Router>
    <Route path='/' component={Main}>
      <IndexRoute component={Home} />
      <Route path='playerOne' component={Prompt} />
      <Route path='playerTwo/:playerOne' component={Prompt} />
      <Route path='battle' component={ConfirmBattle} />
      <Route path='results' component={Results} />
      <Route onEnter={checkAuth} path='dashboard' component={Dashboard} />
    </Route>
  </Router>
)
export default routes

然后在初始化应用的时候,会把路由导入,然后渲染:

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'
ReactDOM.render(routes, document.getElementById('app'))

这就产生了一个问题:静态路由不好用吗?

答案显示是 “不,好用”。但大家仍然会觉得静态路由模式不是 React 的风格。

在 React Router 诞生以来,其作者不仅对构建复杂应用的路由有了更多的经验,同时对 React 本身也有了更多的理解。后面在工作与讨论中发现,React Router 的 API 和 React 背后遵循的一些原则有点背道相驰。再回过头来看下前面的代码,我们将一个 onEnter 的 prop 传给 Route 组件。

<Route onEnter={checkAuth} path='dashboard' component={Dashboard} />

这段代码是指在渲染 dashboard 组件的时候需要经过一层鉴权。这听起来像是在 Dashboard 的 componentDidMount 生命周期里去做一些事情?的确就是这样。

在 React Router 4 之前,它所做的事情有点超出路由这个层面。React Router 4 针对这些问题作出了修正,使得他和 React 相处的更好。如果你了解 React 以及它使用 component 的优势,React Router 4 会让你感觉更加亲切 —— 你需要先忘记你对传统静态路由的一些了解。

现在还有一个问题是:React Router 4 到底做了什么不再让他和 React 有冲突呢?答案是他抛弃了静态路由而开始偏向于使用动态路由,所有的 API 都是基于 component。这也就意味着声明路由的时候就像普通组件一模一样。

简单看下下面的例子:

先从最基本的代码开始,然后为其添加路由功能。

import React, { Component } from 'react'
class App extends Component {
  render() {
    return (
      <div>
        React Rotuer Course
      </div>
    )
  }
}
export default App

和我前面所说的一样,React Router 4 就是一个普通的 component。因此第一件事是 import 我们所需要的东西。

import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'

这里需要注意一些事情。首先,我们 import 了 BrowserRouter 并将之重新命名为 Router。这虽然不是必须的,但在代码中还是很常见的。BrowerRouter 所做的事情就是允许 React Router 将应用的路由信息传给任何他需要的组件(通过 context)。因此,要让 React Router 正常工作,需要在应用程序的根节点中渲染 BrowerRouter

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          React Rotuer Course
        </div>
      </Router>
    )
  }
}
export default App

接下来将使用 RouteRoute 是 React Router 4 背后的支撑。当应用程序的 location 匹配到某个路由的时候,Route 将渲染指定的 component,否则渲染 null。举个例子,当我们应用的路由是 / 的时候要渲染一个 Home 组件,代码会长的像下面这样:

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
const Home = () => (
  <h2>Home</h2>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
        </div>
      </Router>
    )
  }
}
export default App

上面的代码中,如果我们的路径是 / 的时候,将看见 Home 组件。如果不是的话,什么都看不到(Route 会渲染 null)。

下面将加入更多的路由:

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link,
} from 'react-router-dom'
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)
const About = () => (
  <div>
    <h2>About</h2>
  </div>
)
const Topics = () => (
  <div>
    <h2>Topics</h2>
  </div>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={Home} />
          <Route path='/about' component={About} />
          <Route path='/topics' component={Topics} />
        </div>
      </Router>
    )
  }
}
export default App

如上,如果想在应用中加入更多的路由,只需要渲染更多的 Route 组件。如果你还没有忘记静态路由,可能会对渲染路由这种事情感到奇怪。

你只需要记住 Route 只是一个具有渲染方法的普通 React 组件,该渲染方法渲染组件还是 null 取决于 path 是否匹配。因为在上面的例子中,要么渲染组件,要么渲染 null

在上面代码中有一个点需要注意:运行这个程序的时候,前往 about 路径,About 组件和 Home 组件都会被渲染。这是因为即使 / 不是完全匹配的,但仍旧会认为是部分匹配,所以 Home 组件也会被渲染。为了解决这种问题,需要在 /Route 中添加一个 exact 的 prop,来确保只有完全匹配的时候才会渲染。

<Route exact path='/' component={Home} />

现在我们通过应用的 location 在动态的渲染 UI,下一件事情就是如何去改变应用的 location。这正是 Link 组件所要做的事情,它是一个允许用户声明性的浏览应用的组件。现在,使用 Link 添加一个简单的导航吧。

render() {
  return (
    <Router>
      <div>
        <ul>
          <li><Link to='/'>Home</Link></li>
          <li><Link to='/about'>About</Link></li>
          <li><Link to='/topics'>Topics</Link></li>
        </ul>
        <Route path='/' component={Home} />
        <Route path='/about' component={About} />
        <Route path='/topics' component={Topics} />
      </div>
    </Router>
  )
}

事实上现在已经介绍完了 React Router 4 的基本操作。我们基于应用的 location 使用 Route 去渲染不同的组件,并且通过 Link 组件来更改应用的 location。

更深一点,一起看看嵌套的路由。嵌套路由是 React Router 之前版本的一个基础功能,今天它仍然也是。与之前版本相比,最大的区别就是现在创建嵌套路由的方式。在之前的版本中,只需要在路由配置中嵌套的使用路由,但今天有雨 React Router 4 是动态路由,所以这样做是行不通的。但是就我而言,觉得 React Router 4 的嵌套路由比之前版本的更加直接。再次强调一下:忘记之前你对静态路由了解的一切。

再看一下我们的例子,如果我们想要 Topics 组件渲染一个嵌套的 导航和其他的一些路由该怎么做呢?不需要很复杂,就像嵌套一个 div 一样,你只需要嵌套使用 Route

const Topic = () => {
  <div>
    <h3>TOPIC</h3>
  </div>
}
const Topics = () => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`/topics/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`/topics/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`/topics/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>
    <Route path={`/topics/rendering`} component={Topic} />
    <Route path={`/topics/components`} component={Topic} />
    <Route path={`/topics/props-v-state`} component={Topic} />
  </div>
)

现在当用户导航到 /topics 时,将看到一个嵌套的导航栏,UI 也会随着 location 的变化而自动改变。唯一的区别是我们现在正在通过 React Router 在一个组件内部渲染 navbarRoute

你可能会注意到我们都是在硬编码 URL,而不是通过当前嵌套的位置来动态创建。React Router 在渲染一个组件的时候,它会传递三个东西:matchlocationhistory。在这个例子中,我们想要的是 match.url,它会给我们当前 URL 中匹配的部分(在我们的例子中,//topics)。所以在任何我们不好硬编码 /topic 的地方,都可以使用 match.url 替换。

const Topic = () => {
  <div>
    <h3>TOPIC</h3>
  </div>
}
const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>
    <Route path={`${match.url}/rendering`} component={Topic} />
    <Route path={`${match.url}/components`} component={Topic} />
    <Route path={`${match.url}/props-v-state`} component={Topic} />
  </div>
)

还有另外一件事情你可能会注意到:即使渲染相同的组件,我们也在渲染三个不同的 Route,他们之前唯一的区别是嵌套的 URL。下面是使用 url 参数的经典例子。

const Topics = ({ match }) => (
  <div>
    ...
    <Route path={`${match.url}/:topicId`} component={Topic} />
  </div>
)

React Router 渲染 Topic 组件时,使用了之前介绍过的 match 属性,与此类似,还可以使用 match.params 下的 topicId

const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)

最后,当我们处于 /topics 路由时,如果某个主题还未被选中,我们想渲染一个文字,比如:Please select a topic。我们可以创建一个渲染文本的组件或者使用 Routerender 属性:

<Route 
  exact 
  path={match.url} 
  render={() => ( <h3>Please select a topic.</h3> )}
/>

就这样,我们酷酷的代码会长得像这样:

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)
const About = () => (
  <div>
    <h2>About</h2>
  </div>
)
const Topic = ({ match }) => (
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)
const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>
    <Route path={`${match.url}/:topicId`} component={Topic}/>
    <Route exact path={match.url} render={() => (
      <h3>Please select a topic.</h3>
    )}/>
  </div>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/topics">Topics</Link></li>
          </ul>
          <hr/>
          <Route exact path="/" component={Home}/>
          <Route path="/about" component={About}/>
          <Route path="/topics" component={Topics}/>
        </div>
      </Router>
    )
  }
}
export default App

React Router 是一个以 component 为 API 的 Router,是一个真正意义上的 React Router。我相信 React 会让你成为更好的 JavaScript 开发者,而 React Router 4 会让你成为更好的 React 开发者。

想要与作者交流?点击 这里查看原文 了解更多