React Router v4入门学习记录: 单页应用的路由实践

React Router V4(俗称 第四代react-router)相对V2/V3几乎完全重写了,遵循 Just Component 的 API 设计理念。

本篇文章中,我们会构建一个简单的单页应用,来理解React Router的基本使用,内容:

  • 选择哪种路由: HashRouter、BrowserRouter
  • Route的核心内容: path、exact、component、render等
  • 使用Link实现路由跳转

一、如何构建单页应用?

假如你还不会构建一个简单的单页应用,请参考笔者之前的React入门文章 - react入门教程01 - 简介、开发环境搭建、创建第一个React应用

二、完整代码实现

codesandbox

三、安装

React Router V4被分成了三个包: react-routerreact-router-domreact-router-native

其中第一个react-router是不能被直接安装的。该包提供了路由的核心组件和方法。另外两个包分别提供了两个环境的路由: 浏览器原生

这里,我们构建单页网页,只需要安装react-router-dom

// npm
npm install --save react-router-dom

// yarn 
yarn add react-router-dom

四、核心概念

4.1 Router

开始具体的代码实现之前,我们需要决定使用哪一类型路由。在浏览器运行环境中,有两种路由组件,BrowserRouterHashRouter组件。在下面的单页应用中,我们使用BrowserRouter

4.2 History

每个路由对象会创建一个历史history对象,<BrowserRouter> 创建 browser history, <HashRouter> 创建 hash history。若你想深入理解history,参考这篇文章

4.3 Routes

在V3中,<Route>不是一个真正的组件,而是作为一个标签用来创建route配置对象。

使用 v4,您可以像常规的 React 程序一样布局您应用中的组件,您要根据位置(特别是其 pathname )呈现内容的任何位置,您将呈现 <Route>

在 v4,<Route> 其实是一个组件,所以无论你在哪里渲染 <Route>,它里面的内容都会被渲染。当 <Route> 的 path 与当前的路径匹配时,它将会渲染 component, render, orchildren 属性中的内容,当 <Route> 的 path 与当前的路径不匹配时,将会渲染 null

4.4 Path

Router需要一个path字符串属性,描述路由的路径地址。比如,<Route path='/roster' />会匹配以/roster为前缀的路径地址。当匹配到当前的路径时,路由会渲染一个React元素;不匹配的话,就不会渲染。

<Route path='/roster'/>
// when the pathname is '/', the path does not match
// when the pathname is '/roster' or '/roster/2', the path matches
// If you only want to match '/roster', then you need to use
// the "exact" prop. The following will match '/roster', but not
// '/roster/2'.
<Route exact path='/roster'/>
// You might find yourself adding the exact prop to most routes.
// In the future (i.e. v5), the exact prop will likely be true by
// default. For more information on that, you can check out this 
// GitHub issue:
// https://github.com/ReactTraining/react-router/issues/4958

4.5 match

当路由的path匹配时,会创建一个match对象,该对象的属性如下:

  • url - 当前的路径
  • path - path属性值
  • isExact - path == pathname
  • params - 通过path-to-regexp从路径中抓取的数据

你可以通过[route tester](https://pshrmn.github.io/route-tester/#/)网站来玩转路由match实现

更多路径匹配,请看这里。�

4.6 Switch

在v3中,您可以指定一些子路由,并且只会渲染匹配到的第一个。

v4 通过 <Switch> 组件提供了相似的功能,当 <Switch> 被渲染时,它仅会渲染与当前路径匹配的第一个子 <Route>

4.6 Redirect

如果你想从一个路径重定向到另一个,在V4,你可以使用<Redirect>达到效果。

// v4
<Route exact path="/" render={() => <Redirect to="/welcome" component={App} />} />

<Switch  >
  <Route exact path="/" component={App} />
  <Route path="/login" component={Login} />
  <Redirect path="*" to="/" />
</Switch>

4.7 Link

跳转用的标签。

五、编码部分

接下来,开始实现编码部分。

5.1 <App>

首先,我们先定义一个 <App>组件。为简化实现,我们将应用分成两部分: <Header><Main>。Heander组件包含应用的导航链接;Main组件负责内容的渲染。

import React from 'react'
import Header from './Header'
import Main from './Main'

const App = () => (
  <div>
    <Header />
    <Main />
  </div>
);

export default App

5.2 创建路由

下面,我们定义该单页应用的路径地址:

  • / - 首页
  • /roster - 团队执勤名单
  • /roster/:number - 团队成员的个人页
  • /schedule - 执勤安排表
    <Switch>
      <Route exact path='/' component={Home} />
      <Route exact path='/roster' component={Roster} />
      <Route exact path='/schedule' component={Schedule} />
    </Switch>

Route有三个属性可以用来渲染组件,每个<Route>只能有其中一个属性值。

  • component - 它的值是一个组件,在path匹配成功之后会绘制这个组件;
  • render - 一个返回React组件的方法,跟component的效果差不多,但可以传入“render prop”
  • children - 返回一个React组件的方法,这个总是会绘制,即使没有匹配的路径的时候。
<Route path='/page' component={Page} />

const extraProps = { color: 'red' }
<Route path='/page' render={(props) => (
  <Page {...props} data={extraProps}/>
)}/>

<Route path='/page' children={(props) => (
  props.match
    ? <Page {...props}/>
    : <EmptyPage {...props}/>
)}/>

通常,component属性和render属性会经常被使用,而children属性不太常用。我们暂时不需要传入额外的参数给渲染组件,所以这里的<Route>使用component属性。

上述定义了该应用的基础路由结构,接下来 给出完成的Main组件实现:

import React from 'react'
import { Switch, Route } from 'react-router-dom'
import Home from './Home'
import Roster from './Roster'
import Schedule from './Schedule'

const Main = () => (
  <main>
    <Switch>
      <Route exact path='/' component={Home} />
      <Route exact path='/roster' component={Roster} />
      <Route exact path='/schedule' component={Schedule} />
    </Switch>
  </main>
)

export default Main

5.3 嵌套路由

团队成员的个人页/roster/:number并未在上述的实现中定义。实际上,这个路由会被这个以/roster为前缀的<Roster>组件渲染。

Roster组件中,我们需要渲染两部分内容:

  • /roster - 完整的团队执勤名单页,使用exact完全匹�配
  • /roster/:number - 团队成员的个人页,路由通过抓取/roster后面的地址名字来获取参数
const Roaster = () => (
  <Switch>
    <Route exact path='/roster' component={FullRoster} />
    <Route path='/roster/:number' component={Player} />
  </Switch>
)

这种方式可以很好地将相同前缀的一组路由 统一放入一个组件中实现,更好的管理、归类。

下面是完整的<Roster>实现:

import React from 'react'
import { Switch, Route } from 'react-router-dom'
import FullRoster from './FullRoster'
import Player from './Player'

const Roster = () => (
  <div>
    <h2>This is a roster page!</h2>
    <Switch>
      <Route exact path='/roster' component={FullRoster} />
      <Route path='/roster/:number' component={Player} />
    </Switch>
  </div>
)

export default Roster

5.4 路由参数

有时候我们需要抓取路径地址中的参数,例如,团队成员的个人页需要抓取员工的工号。

/roster/:number中的:number就是需要抓取的员工工号,被储存在match.params.number。例如,/roster/6params数据是: { number: '6' } // note that the captured value is a string

<Player>组件可以使用props.match.params数据来渲染相应员工的数据。

const Player = props => {
  const player = PlayerApi.get(
    parseInt(props.match.params.number, 10)
  );
  if (!player) {
    return (<div>Sorry, but the player was not found.</div>)
  }

  return (
    <div>
      <h1>{player.name} (#{player.number})</h1>
      <h2>Position: {player.position}</h2>
      <Link to='/roster'>Back</Link>
    </div>
  );
};

5.5 <FullRoster>、<Schedule>

其他组件的实现:

// Schedule.js
import React from "react";

const Schedule = () => (
  <div>
    <ul>
      <li>6/5 @ Evergreens</li>
      <li>6/8 vs Kickers</li>
      <li>6/14 @ United</li>
    </ul>
  </div>
);

export default Schedule;

// FullRoster.js
import React from "react";
import { Link } from "react-router-dom";
import PlayerAPI from "../api";

const FullRoaster = () => (
  <div>
    <ul>
      {PlayerAPI.all().map(p => (
        <li key={p.number}>
          <Link to={`/roster/${p.number}`}>{p.name}</Link>
        </li>
      ))}
    </ul>
  </div>
);

export default FullRoaster;

六、最后

我们最后实现<Header>组件使用<Link>元素将所有页面串联起来,当你点击<Link>时,URL更新的同时,仅重新渲染相应的组件,并不需要全部重新渲染。

import React from "react";
import { Link } from "react-router-dom";

// The Header creates links that can be used to navigate
// between routes.
const Header = () => (
  <header>
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/roster">Roster</Link>
        </li>
        <li>
          <Link to="/schedule">Schedule</Link>
        </li>
      </ul>
    </nav>
  </header>
);

export default Header;

七、参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354