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
match
path-to-regexp
URL 参数的规则,页面的状态尽量通过 URL 参数定义。
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')
)