React Router 学习笔记

1,154 阅读10分钟

React Router 学习笔记

路由

路由的原本是一个后端的概念,源于服务器,在服务端中路由描述的是 URL 与处理函数之间的映射关系。通过不同的路由来请求不同的资源。

后端路由

在浏览器地址栏切换不同的 URL 时,每次都要向后台服务器发出请求,服务器响应请求,给浏览器发送相应的资源,浏览器页面也会进行刷新。

举个例子:比如你部署了一个 Apache 的 web 服务器,服务器路径下放了各种 html、js、css 等文件,你可以根据文件路径在浏览器上访问这些资源;又或者你用 PHP、Java 或者 Node.JS 开发一些后台服务器,当你访问对应的 URL 的时候后端就会给你返回你想要的 HTML 文件。

后端路由可以分担一些前端的压力,因为 HTML 文件的拼接,数据的拼接都由服务器来完成。不过当网速比较慢的时候页面加载延时会加剧,用户体验不好。

前端路由

前端路由的本质就是对一些 DOM 的显隐操作,访问不同路径的时候会显示不同的页面组件。随着 SPA(Single Page Application) 的崛起,前端路由也越来越流行,单页应用不仅页面之间数据交互是无刷新的,页面跳转也是无刷新的,这也大大节省了频繁请求后台数据带来的性能开销。前端路由不只是页面切换,更是代码组织的方式。

前端路由的原理

简单的讲就是使用浏览器 API 检测 URL 的变化,截获 URL 地址,然后解析,匹配到相应的页面组件,渲染 DOM。

前端路由有两种模式:hash 模式和 history 模式。

hash 模式

hash 不是指的散列表那个哈希,而是“#”,也被称作锚点,本身就是用来做页面定位的。#后面的值变化会触发 onhsahchange事件,通过 hash 值变化来显示不同的页面(第一次载入执行 load 事件)。

实现一个简单的 hash 路由

import React from 'react'
import ReactDOM from 'react-dom'

class HashRouter {
  constructor(routes) {
    // 当前的URL
    this.currentUrl = ''
    this.routes = routes
    this._refresh = this._refresh.bind(this)
    // 第一次加载的时候执行 load
    window.addEventListener('load', this._refresh, false)
    // hash 路由变化的时候触发
    window.addEventListener('hashchange', this._refresh, false)
  }

  // 获取 hash 路径,如果没有就返回 /
  _getHashPath(url) {
    const index = url.indexOf('#')
    if (index >= 0) {
      return url.slice(index + 1)
    }
    return '/'
  }

  _refresh(event) {
    let curURL = ''
    if (event.newURL) {
      // hash 路由变化,由 hashchange 事件触发
      curURL = this._getHashPath(event.newURL || '')
    } else {
      // 第一次加载的时候进入,由 load 事件触发
      curURL = this._getHashPath(window.location.hash)
    }
    this.currentUrl = curURL

    let route = null
    // 匹配路由
    for (let i = 0; i < this.routes.length; i++) {
      const item = this.routes[i]
      if (this.currentUrl === item.path) {
        route = item
        break
      }
    }
    // 若没有匹配到,则使用最后一个路由
    if (!route) {
      route = this.routes[this.routes.length - 1]
    }
    // 渲染当前的组件
    ReactDOM.render(route.component, document.getElementById('root'))
  }
}

// 先定义几个路由
const routes = [
  {
    path: '/',
    name: 'home',
    component: <Home />,
  },
  {
    path: '/about',
    name: 'about',
    component: <About />,
  },
  {
    path: '*',
    name: '404',
    component: <NotFound404 />,
  },
]

new HashRouter(routes)

function Home() {
  return (
    <h1>Home</h1>
  )
}

function About() {
  return (
    <h1>About</h1>
  )
}

function NotFound404() {
  return (
    <h1>NotFound404</h1>
  )
}

history 模式

history 模式会用到 window.history 中的方法:

  • back():后退到上一个路由;
  • forward():前进到下一个路由,如果有的话;
  • go(number):进入到任意一个路由,正数为前进,负数为后退;
  • pushState(obj, title, url):前进到指定的 URL,不刷新页面;
  • replaceState(obj, title, url):用 URL 替换当前的路由,不刷新页面;

这五种方法都可以修改页面 URL,而不发送请求。

如果服务端没有新更新的 URL 时,一刷新浏览器就会报错,因为刷新浏览器后,是真实地向服务器发送了一个 HTTP 的网页请求。因此若要使用 history 路由,需要服务端的支持。

第一次载入执行 load 事件;

popstate 事件可以监听 back、forward 还有 go 事件;

pushState 与 replaceState 无法被监听到,所以两个方法的监听需要我们自己来实现。window.dispatchEvent是一个事件触发器,可以借助它来实现。下面代码是对window.dispatchEvent使用的一个演示:

const listener = function (type) {
  // 准备被监听的原始操作
  const origin = window.history[type]
  return function () {
    // arguments 是执行的时候传入的参数
    const func = origin.apply(this, arguments)
    const newEvent = new Event(type)
    newEvent.arguments = arguments
    window.dispatchEvent(newEvent)
    return func
  }
}
window.history.pushState = listener('pushState')
window.history.replaceState = listener('replaceState')

window.addEventListener('pushState', (e) => {
  console.log(e)
}, false)

window.addEventListener('replaceState', (e) => {
  console.log(e)
}, false)

window.history.pushState({ text: 'pushState' }, 'pushState', '/pushState')
window.history.replaceState({ text: 'replaceState' }, 'replaceState', '/replaceState')

实现一个简单的 history 路由

import React from 'react'
import ReactDOM from 'react-dom'

// 先定义几个路由
const routes = [
  {
    path: '/',
    name: 'home',
    component: <Home />,
  },
  {
    path: '/about',
    name: 'about',
    component: <About />,
  },
  {
    path: '*',
    name: '404',
    component: <NotFound404 />,
  },
]

function Home() {
  return (
    <h1>Home</h1>
  )
}

function About() {
  return (
    <h1>About</h1>
  )
}

function NotFound404() {
  return (
    <h1>NotFound404</h1>
  )
}

class HistoryRouter {
  constructor(routes) {
    this.routes = routes
    this.currentUrl = ''
    this._refresh = this._refresh.bind(this)
    this._addStateListener()
    window.addEventListener('load', this._refresh, false)
    window.addEventListener('popstate', this._refresh, false)
    window.addEventListener('pushState', this._refresh, false)
    window.addEventListener('replaceState', this._refresh, false)
  }

  _addStateListener() {
    const listener = function (type) {
      // 准备被监听的原始操作
      const origin = window.history[type]
      return function () {
        // arguments 是执行的时候传入的参数
        const func = origin.apply(this, arguments)
        const newEvent = new Event(type)
        newEvent.arguments = arguments
        window.dispatchEvent(newEvent)
        return func
      }
    }
    window.history.pushState = listener('pushState')
    window.history.replaceState = listener('replaceState')
  }

  _refresh() {
    this.currentUrl = window.location.pathname
    let route = null
    // 匹配路由
    for (let i = 0; i < this.routes.length; i++) {
      const item = this.routes[i]
      if (this.currentUrl === item.path) {
        route = item
        break
      }
    }
    // 若没有匹配到,则使用最后一个路由
    if (!route) {
      route = this.routes[this.routes.length - 1]
    }
    // 渲染当前的组件
    ReactDOM.render(route.component, document.getElementById('root'))
  }
}
new HistoryRouter(routes)

如果是用的 webpack 的开发环境那么,这段代码是可以正常执行的,即便是刷新页面也可以正常返回页面,这是 webpack dev server 做了处理的。

如果是使用其它的 web 服务器,那么需要自己进行一些配置。

// 如果使用 nginx 作为 web 服务器,可以使用以下代码在控制台进行页面切换,不过一刷新就会404
window.history.pushState({}, '', '/about') // 显示 about 界面

window.history.pushState({}, '', '/') // 显示 home 界面

window.history.pushState({}, '', '/abc') // 显示 NotFound404 界面

以 nginx 为例,可以将配置文件进行一下修改,在找不到页面的时候还是返回 index.html 界面:

默认如下:
#error_page  404              /404.html;

修改为如下即可:
error_page  404              /index.html; 

React Router

React Router 的内容其实还蛮多的,官方的 reactrouter.com/ 文档将这这 Router 分为三类:CORE、WEB、NATIVE。CORE 是后端用的,比如:node;WEB 则是前端路由;NATIVE 是手机应用。而本文将重点关注WEB 类型的 Router。

Router

react-router提供,这是 Router 组件的公共低阶接口,通常应用程序会使用高阶路由之一:

  • BrowserRouter
  • HashRouter
  • MemoryRouter
  • NativeRouter
  • StaticRouter
import React from 'react'
import ReactDOM from 'react-dom'
import { Router } from 'react-router'
import { createBrowserHistory } from 'history'

const history = createBrowserHistory();

ReactDOM.render(
  <Router history={history}>
    <Home />
  </Router>,
  document.getElementById('root')
)

function Home() {
  return (
    <h1>Home</h1>
  )
}

BrowserRouter

history 模式的路由,使用 HTML5 的history API。

属性:

  • basename
  • forceRefresh
  • getUserConfirmation 与 <Prompt>配合使用
  • keyLength
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route } from 'react-router-dom'

function App() {
  return (
    <>
      <Router
        basename={'/basename'} // 配置此路径之后所有路径都要加此前缀访问否则会有警告也可能导致某些情况下无法访问组件
      >
        <Route path="/">
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
      </Router>
    </>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

function Home() {
  return (
    <h1>Home</h1>
  )
}

function About() {
  return (
    <h1>About</h1>
  )
}

访问 http://localhost:3000/basename/ 可以看到 Home 界面;

访问 http://localhost:3000/basename/about 可以看到 Home 与 About 界面;

HashRouter

hash 模式路由。

属性:

  • basename
  • getUserConfirmation 与 <Prompt>配合使用
  • hashType,默认"slash"(#/),还可以是 "noslash"(#)、"hashbang"(#!/)
import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter as Router, Route } from 'react-router-dom'

function App() {
  return (
    <>
      <Router
        basename={'/basename'} // 配置此路径之后所有路径都要加此前缀访问否则会有警告也可能导致某些情况下无法访问组件
      >
        <Route path="/">
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
      </Router>
    </>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

function Home() {
  return (
    <h1>Home</h1>
  )
}

function About() {
  return (
    <h1>About</h1>
  )
}

访问 http://localhost:3000/#/basename/ 可以看到 Home 界面;

访问 http://localhost:3000/#/basename/about 可以看到 Home 与 About 界面;

MemoryRouter

不会读写地址栏的 URL ,多用在非浏览器环境像 React Native。但是浏览器环境也可以用,比如下面这个例子:

import React from 'react'
import ReactDOM from 'react-dom'
import { MemoryRouter as Router, Route, Link } from 'react-router-dom'

function App() {
  return (
    <>
      <Router
        initialEntries={["/one", "/two", { pathname: "/three" }]}
        initialIndex={1}
      >
        <Route path="/one">
          <One />
        </Route>
        <Route path="/two">
          <Two />
        </Route>
        <Route path="/three">
          <Three />
        </Route>
        <Link to="/one">one</Link>
        <br />
        <Link to="/two">two</Link>
        <br />
        <Link to="/three">three</Link>
      </Router>
    </>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

function One() {
  return (
    <h1>One</h1>
  )
}

function Two() {
  return (
    <h1>Two</h1>
  )
}

function Three() {
  return (
    <h1>Three</h1>
  )
}

访问 http://localhost:3000/ 可以看到 Two 界面,然后可以点击链接切换内容,但是 URL 不会发生变化。

属性:

  • initialEntries 一个路由的对象数组,数组字段可以有pathname, search, hash, state,也可以是一个字符串 URL 路径
  • initialIndex 初始索引
  • getUserConfirmation
  • keyLength

NativeRouter

React Native 构建的 IOS 和 Android 应用使用的一个路由。

import { NativeRouter } from 'react-router-native'

<NativeRouter>
  <App />
</NativeRouter>

属性:

  • getUserConfirmation 与 <Prompt>配合使用
  • keyLength

StaticRouter

这是在服务端中很有用的一个,用户不会实际点击,所以位置永远不会改变。

下面是官方的一个 node 例子:

import http from "http";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router";

http
  .createServer((req, res) => {
    // This context object contains the results of the render
    const context = {};

    const html = ReactDOMServer.renderToString(
      <StaticRouter location={req.url} context={context}>
        <App />
      </StaticRouter>
    );

    // context.url will contain the URL to redirect to if a <Redirect> was used
    if (context.url) {
      res.writeHead(302, {
        Location: context.url
      });
      res.end();
    } else {
      res.write(html);
      res.end();
    }
  })
  .listen(3000);

属性:

  • basename
  • location: string
  • location: object
  • context: object

Route

React Router 中最重要的组件,其作用是根据路径匹配呈现对应的UI。

属性:

  • path: string | string []
  • exact: bool 是否精确匹配 URL
  • component 普通的 path 只能通过 location 匹配,而此属性则可以使用 match、location、history 等属性
  • render: func 行内渲染组件
  • children: func 是否能匹配到路由都将显示的内容
  • strict: bool 带有尾部 “/” 的 path 只能匹配带有尾部 “/” 的 URL
  • location: object
  • sensitive: bool URL 是否大小写敏感

component 和 render 优先于 children,因此不要在同一个<Route>中使用多个

例子:

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

// All route props (match, location and history) are available to User
function User(props) {
  console.log(props)
  return <h1>Hello {props.match.params.username}!</h1>
}

ReactDOM.render(
  <Router>
    {/* 普通 path */}
    <Route path='/mypath'>
      <MyPath />
    </Route>
    {/* 数组 path 数组内路径都可以匹配 */}
    <Route path={['/arrpath0', '/arrpath1']}>
      <ArrPath />
    </Route>
    {/* 精确匹配,exact后面再加子路径将不会匹配 */}
    <Route exact path='/exact'>
      <Exact />
    </Route>
    {/* 带尾部 “/” 才能匹配上 */}
    <Route strict path='/strict/'>
      <Strict />
    </Route>
    {/* 大小写敏感 */}
    <Route sensitive path='/Sensitive'>
      <Sensitive />
    </Route>
    {/* component 允许组件使用更多属性 */}
    <Route path='/user/:username' component={User} />
    {/* render 允许内联组件 */}
    <Route path='/render' render={() => <div>Render</div>} />
    {/* 无论是否匹配路由都将显示 */}
    <Route children={Children} />
  </Router>,
  document.getElementById('root')
)

function MyPath() {
  return (
    <h1>MyPath</h1>
  )
}

function Exact() {
  return (
    <h1>Exact</h1>
  )
}

function Strict() {
  return (
    <h1>Strict</h1>
  )
}

function Sensitive() {
  return (
    <h1>Sensitive</h1>
  )
}

function ArrPath() {
  return (
    <h1>ArrPath</h1>
  )
}

function Children() {
  return (
    <h1>Children</h1>
  )
}

Link

提供一个声明式、可访问的导航链接。

  • to: string 字符串形式的链接地址
  • to: object 对象形式的链接地址
  • to: function 函数形式的连接地址
  • replace: bool 默认为 false,如果为 true 点击链接后将替换历史栈中当前条目,而是不添加新条目
  • innerRef: function 访问组件的 DOM 元素
  • innerRef: RefObject
  • component: React.Component 可以使用自己的导航组件
  • others 可以传一些 id、title、className 等属性

例子:

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

const link5Ref = node => {
  console.log(node)
}

const link6Ref = React.createRef()

const FancyLink = React.forwardRef((props, ref) => (
  <a href='/link7' ref={ref}>Link7{props.children}</a>
))

ReactDOM.render(
  <Router>
    <Link to="/link1">Link1</Link>
    <br />
    <Link to={{
      pathname: '/link2',
      search: '?foo=bar',
      hash: '#hello-hash',
      state: {},
    }}>Link2</Link>
    <br />
    <Link to={location => {console.log(location); return '/link3'}}>Link3</Link>
    <br />
    {/* window.history.length 不会增加 */}
    <Link to="/link4" replace>Link4</Link>
    <br />
    <Link to="/link5" innerRef={link5Ref}>Link5</Link>
    <br />
    <Link to="/link6" innerRef={link6Ref}>Link6</Link>
    <br />
    <Link to="/link7" component={FancyLink} />
    <br />
    <Link to="/link8" title="link8" style={{ fontSize: '2rem' }}>Link8</Link>
    <Route path='/link1'>
      <Link1 />
    </Route>
    <Route path='/link2'>
      <Link2 />
    </Route>
    <Route path='/link3'>
      <Link3 />
    </Route>
    <Route path='/link4'>
      <Link4 />
    </Route>
    <Route path='/link5'>
      <Link5 />
    </Route>
    <Route path='/link6'>
      <Link6 />
    </Route>
    <Route path='/link7'>
      <Link7 />
    </Route>
    <Route path='/link8'>
      <Link8 />
    </Route>
  </Router>,
  document.getElementById('root')
)

function Link1() {
  return (
    <h1>Link1</h1>
  )
}

function Link2() {
  return (
    <h1>Link2</h1>
  )
}

function Link3() {
  return (
    <h1>Link3</h1>
  )
}

function Link4() {
  return (
    <h1>Link4</h1>
  )
}

function Link5() {
  return (
    <h1>Link5</h1>
  )
}

function Link6() {
  return (
    <h1>Link6</h1>
  )
}

function Link7() {
  return (
    <h1>Link7</h1>
  )
}

function Link8() {
  return (
    <h1>Link8</h1>
  )
}

NavLink

这是一个特殊版本的<Link>,当它与当前 URL 匹配的时候可以加上样式来渲染一些元素。

  • activeClassName: string 激活之后的 class 名
  • activeStyle: object 激活之后的内联样式
  • exact: bool 是否精确匹配 URL 之后样式才会生效
  • strict: bool 带有尾部 “/” 的 path 只能匹配带有尾部 “/” 的 URL 样式才会生效
  • isActive: func 添加额外的激活此链接的函数
  • location: object
  • aria-current: string

例子:

style.css

.selected {
  color: aqua;
}
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import './style.css'

const activeSty = {
  color: 'orange'
}

const exactSty = {
  color: 'red'
}

const strictSty = {
  color: 'yellow'
}

const isActiveSty = {
  color: 'bisque'
}

const isActiveFun = (match, location) => {
  console.log(match)
  console.log(location)
  if (!match) {
    return false
  }
  return true
}

ReactDOM.render(
  <Router>
    <NavLink to="/navlink1" activeClassName="selected">NavLink1</NavLink>
    <br />
    <NavLink to='/navlink2' activeStyle={activeSty}>NavLink2</NavLink>
    <br />
    <NavLink to='/navlink3' exact activeStyle={exactSty}>NavLink3</NavLink>
    <br />
    <NavLink to='/navlink4/' strict activeStyle={strictSty}>NavLink4</NavLink>
    <br />
    <NavLink to='/navlink5' isActive={isActiveFun} activeStyle={isActiveSty}>NavLink5</NavLink>
    <br />
    <Route path='/navlink1'>
      <NavLink1 />
    </Route>
    <Route path='/navlink2'>
      <NavLink2 />
    </Route>
    <Route path='/navlink3'>
      <NavLink3 />
    </Route>
    <Route path='/navlink4/'>
      <NavLink4 />
    </Route>
    <Route path='/navlink5'>
      <NavLink5 />
    </Route>
  </Router>,
  document.getElementById('root')
)

function NavLink1() {
  return (
    <h1>NavLink1</h1>
  )
}

function NavLink2() {
  return (
    <h1>NavLink2</h1>
  )
}

function NavLink3() {
  return (
    <h1>NavLink3</h1>
  )
}

function NavLink4() {
  return (
    <h1>NavLink4</h1>
  )
}

function NavLink5() {
  return (
    <h1>NavLink5</h1>
  )
}

Prompt

Prompt 意为提示,用于在位置跳转之前给予用户一些提示信息。与 Router 的 getConfirmation 配合使用。

  • message: string 提示信息
  • message: func 需要返回一个字符串以提示用户,或者返回 true 以允许直接跳转
  • when: bool false 就不显示,true就显示

例子:

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

const getConfirmation = (message, callback) => {
  // this is the default behavior
  const allowTransition = window.confirm(message)
  console.log(allowTransition)
  callback(allowTransition)
}

const promptFunc = (location, action) => {
  console.log(location)
  console.log(action)
  if (action === 'PUSH') {
    return "Are you sure you want to leave home?"
  } 
  if (action === 'POP') {
    return "Are you sure you want to leave prompt?"
  }
}

ReactDOM.render(
  <Router getUserConfirmation={getConfirmation}>
    <div>
      {/* <Prompt when={true} message="Are you sure you want to leave?" /> */}
      <Prompt when={true} message={promptFunc} />
      <Link to="/prompt">Prompt</Link>
      <Route path="/" exact>
        <Home />
      </Route>
      <Route exact path="/prompt" component={Prompt1} />
    </div>
  </Router>,
  document.getElementById('root')
)

function Home() {
  return (
    <h1>Home</h1>
  )
}

function Prompt1() {
  return (
    <h1>Prompt1</h1>
  )
}

Redirect

会导航到一个新的的位置,新的位置将覆盖历史栈中的当前条目,就像服务器端的重定向(HTTP 3xx)。只能在 <Switch>中使用。

  • to: string URL 路径
  • to: object 对象形式的 URL 路径
  • push: bool 如果为 true,重定向会将新的位置推入历史记录,而不是替换当前条目
  • from: string URL 路径
  • exact: bool 是否精确匹配 URL
  • strict: bool 带有尾部 “/” 的 path 只能匹配带有尾部 “/” 的 URL
  • sensitive: bool URL 是否大小写敏感

例子:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'

ReactDOM.render(
  <Router>
    <Switch>
      <Redirect from="/one" to="/home1" />
      <Redirect exact from="/two" to="/home2" />
      <Redirect strict from="/three/" to="/home3" />
      <Redirect sensitive from="/Four" to="/home4" />
      <Route path="/home1">
        <Home1 />
      </Route>
      <Route path="/home2">
        <Home2 />
      </Route>
      <Route path="/home3">
        <Home3 />
      </Route>
      <Route path="/home4">
        <Home4 />
      </Route>
      <Route path="/five">
        <Redirect to="/home1" />
      </Route>
    </Switch>
  </Router>,
  document.getElementById('root')
)

function Home1() {
  return (
    <h1>Home1</h1>
  )
}

function Home2() {
  return (
    <h1>Home2</h1>
  )
}

function Home3() {
  return (
    <h1>Home3</h1>
  )
}

function Home4() {
  return (
    <h1>Home4</h1>
  )
}

Switch

用于渲染与路径匹配的第一个子 <Route><Redirect

如果仅仅使用 <Route> 那么路径匹配的都会被渲染,如果放到 <Switch> 里面则只会渲染一个路由。

  • location: object
  • children: node

例子:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

ReactDOM.render(
  <Router>
    <Route path="/">
      <Route1 />
    </Route>
    <Route path="/route2">
      <Route2 />
    </Route>
    <Switch>
      <Route path="/">
        <Switch1 />
      </Route>
      <Route path="/route2">
        <Switch2 />
      </Route>
    </Switch>
  </Router>,
  document.getElementById('root')
)

function Route1() {
  return (
    <h1>Route1</h1>
  )
}

function Route2() {
  return (
    <h1>Route2</h1>
  )
}

function Switch1() {
  return (
    <h1>Switch1</h1>
  )
}

function Switch2() {
  return (
    <h1>Switch2</h1>
  )
}

访问 http://localhost:3000/route2

<Switch>外的路由全部匹配了,而<Switch>内的匹配一个之后便不再继续匹配了。

常用属性与方法

generatePath

import { generatePath } from "react-router";

const path = generatePath("/user/:id/:entity(posts|comments)", {
  id: 1,
  entity: "posts"
});
console.log(path) // /user/1/posts

location

{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere',
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}

matchPath

import { matchPath } from "react-router";

matchPath("/users/2", {
  path: "/users/:id",
  exact: true,
  strict: true
});

//  {
//    isExact: true
//    params: {
//        id: "2"
//    }
//    path: "/users/:id"
//    url: "/users/2"
//  }
matchPath("/users", {
  path: "/users/:id",
  exact: true,
  strict: true
});

//  null

withRouter

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, withRouter } from 'react-router-dom'

export const ButtonWithRouter = withRouter(({ history }) => {
  console.log('history', history)
  return (
    <button
      type='default'
      onClick={() => { history.push('/home') }}
    >
      Click Me!
    </button>

  )
})

ReactDOM.render(
  <>
    <Router>
    <ButtonWithRouter />
      <Route path="/home">
        <Home1 />
      </Route>
    </Router>
  </>,
  document.getElementById('root')
)

function Home1() {
  return (
    <h1>Home1</h1>
  )
}

history

history

match

match

path-to-regexp

URL 参数的规则,页面的状态尽量通过 URL 参数定义。

path-to-regexp

Hooks

useHistory

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, useHistory } from 'react-router-dom'

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

ReactDOM.render(
  <>
    <Router>
    <HomeButton />
      <Route path="/home">
        <Home1 />
      </Route>
    </Router>
  </>,
  document.getElementById('root')
)

function Home1() {
  return (
    <h1>Home1</h1>
  )
}

useLocation

import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import {
  BrowserRouter as Router,
  Switch,
  useLocation,
  Route,
} from "react-router-dom"

function App() {
  let location = useLocation()
  useEffect(() => {
    console.log(location)
  }, [location])
  return (
    <Switch>
      <Route path="/home">
        <Home1 />
      </Route>
    </Switch>
  )
}

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
)

function Home1() {
  return (
    <h1>Home1</h1>
  )
}

useParams

import React from 'react'
import ReactDOM from 'react-dom'
import {
  BrowserRouter as Router,
  Switch,
  useParams,
  Route,
} from "react-router-dom"

function BlogPost() {
  let { slug } = useParams()
  return <div>Now showing post {slug}</div>
}

ReactDOM.render(
  <Router>
    <Switch>
      {/* 访问 http://localhost:3000/ */}
      <Route exact path="/">
        <HomePage />
      </Route>
      {/* 访问 http://localhost:3000/blog/xxx */}
      <Route path="/blog/:slug">
        <BlogPost />
      </Route>
    </Switch>
  </Router>,
  document.getElementById('root')
)

function HomePage() {
  return (
    <h1>Home</h1>
  )
}

useRouteMatch

import React from 'react'
import ReactDOM from 'react-dom'
import {
  BrowserRouter as Router,
  useRouteMatch,
} from "react-router-dom"

function BlogPost() {
  let match = useRouteMatch("/blog/:slug")
  // Do whatever you want with the match...
  console.log(match)
  return (
    <h1>Home, { match?.params?.slug }</h1>
  )
}

ReactDOM.render(
  <Router>
    <BlogPost />
  </Router>,
  document.getElementById('root')
)

嵌套路由

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

function Level1() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/level1/1">Level1-1</Link>
          </li>
          <li>
            <Link to="/level1/2">Level1-2</Link>
          </li>
          <li>
            <Link to="/level1/3">Level1-3</Link>
          </li>
        </ul>

        <div>
          <Route path="/level1/:id" component={Level2} />
        </div>
      </div>
    </Router>
  )
}

function Level2(props) {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to={`/level1/${props.match.params.id}/level2/1`}>Level2-1</Link>
          </li>
          <li>
            <Link to={`/level1/${props.match.params.id}/level2/2`}>Level2-2</Link>
          </li>
          <li>
            <Link to={`/level1/${props.match.params.id}/level2/3`}>Level2-3</Link>
          </li>
        </ul>

        <div>
          <Route path="/level1/:id/level2/:subId" component={SubComponent} />
        </div>
      </div>
    </Router>
  )
}

function SubComponent(props) {
  return (
    <h1>{`Level ${props.match.params.id} - ${props.match.params.subId}`}</h1>
  )
}

ReactDOM.render(
  <Router>
    <Level1 />
  </Router>,
  document.getElementById('root')
)