官方文档
首先我们创建一个
react
的项目. 这里使用create-react-app
来快速创建
npx create-react-app react-router-dom-demo
然后我们需要下载安装
react-router-dom
cd react-router-dom-demo
yarn add react-router-dom
启动项目
yarn start
Demo Github地址
BaseRoute -- 基本路由
import React from "react";
import {
//as的作用为将HashRouter重命名为Router,这样的好处是在反复测试HashRouter和BrowserRouter时,可以免去组件修改
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/dashboard">
<Dashboard />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
</div>
);
}
UrlParameters -- 动态路由
获取路由参数可以使用hooks形式
:param
,也可以用原始的props.match.params.xxx
注意 :
<Route></Route>
标签的子元素如果使用children={}
的话 原始方法props.match.params.xxx
就不适用只能使用useParams()
的方式. 关于 Route 标签的属性后续进行说明
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from "react-router-dom";
export default class UrlParameters extends Component {
render() {
return (
<Router>
<div>
<h2>Accounts</h2>
<ul>
<li>
<Link to="/netflix">Netflix</Link>
</li>
<li>
<Link to="/zillow-group">Zillow Group</Link>
</li>
<li>
<Link to="/yahoo">Yahoo</Link>
</li>
<li>
<Link to="/modus-create">Modus Create</Link>
</li>
</ul>
<Switch>
<Route path="/:id" component={Child} />
</Switch>
</div>
</Router>
)
}
}
function Child(props) {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
let idd = props.match.params.id;
return (
<div>
<h3>ID: {id} ,other:{idd}</h3>
</div>
);
}
NestingRouter -- 嵌套路由
嵌套路由适用于有明显层级划分的情况. 简单来说就是路由下面还有子路由的情况.
案例中用到的新的 hooks:
useRouteMatch
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from "react-router-dom";
export default class NestingRouter extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
)
}
}
function Home(){
return(
<div>
<h2>Home</h2>
</div>
)
}
function Topics(){
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch()
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
)
}
function Topic(){
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
我们在 Topics 组件中打印一下
useRouteMatch
结果如下
path 和 url 的值是一样的, 官方的说明则是 :
path
使我们能够建立相对于父路线的<Route>
路径,而url
使我们能够建立相对的链接。这段话翻译过来大概意思就是:path
是用来获取父路径的.url
是用于Link
组件的跳转的.
RedirectRoute(Auth) -- 重定向路由(鉴权)
首先分析一下页面逻辑:
有三个页面 一个公共页面PublicPage
; 一个登陆页面LoginPage
; 还有一个受保护页面ProtectedPage
点击查看受保护页面 必须登录过后才能看
接下来在代码里面一步一步分析
我将官方给的案例稍微改动了一下 因为AuthButton 这个组件 重新登录什么的没有重新渲染到 所以这个组件就永远不会变 我将它放入 PrivateRoute 内 进行展示就可以 (分析在代码的注释内)
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useHistory,
useLocation
} from "react-router-dom";
//Redirect 会导航到新的位置,重定向
//useHistory 该useHistory挂钩使您可以访问history可用于导航的实例
//useLocation钩子返回表示当前URL的location对象。您可以将其看作是useState,它在URL出现时返回一个新位置改变。这个可能非常有用,例如,在您希望在每次加载新页面时使用web分析工具触发新的“页面视图”事件的情况下
export default class RedirectRouter extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<AuthButton />
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
)
}
}
//定义变量以及登录退出方法
const fakeAuth = {
isAuthenticated: false, //是否登录字段
authenticate(cb) { //登录方法
fakeAuth.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) { //退出登录方法
fakeAuth.isAuthenticated = false;
setTimeout(cb, 100);
}
}
//登录控制按钮组件
function AuthButton() {
let history = useHistory();
return fakeAuth.isAuthenticated?(
<p>
Welcome!
<button
onClick={() => {
// fakeAuth.signout(() => history.push("/protected"));
fakeAuth.signout(() => history.push("/protected")); //如果已经登录成功就显示 sign out 按钮 点击调用退出登录方法 isAuthenticated 置为 false, 异步回调 history.push 进行页面跳转
}}
>
Sign out
</button>
</p>
):(
//如果登录失败 显示你没有登录
<p>You are not logged in.</p>
)
}
//自定义高阶路由组件 {children, ...rest} children 表示父组件下面的所有子组件 此处是指ProtectedPage组件 ...rest 是父组件上的所有属性 此处是指 path="/protected"
function PrivateRoute( {children,...rest} ) {
// let history = useHistory();
// console.log("history:",history)
return (
<Route
{...rest}
render={({ location }) =>
fakeAuth.isAuthenticated ? (
children
) : ( // 这里 我们可以将退出登录的地方AuthButton组件的 onClick 事件修改一下 push 到 /protected 这样我们就能看到这个组件的切换
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
//公共页面组件
function PublicPage() {
return <h3>Public</h3>;
}
//受保护页面组件
function ProtectedPage() {
return <h3>Protected</h3>;
}
//登录页面组件
function LoginPage() {
let history = useHistory();
let location = useLocation();
// console.log("history:",history)
// console.log("location:",location)
//有 state 属性的话就将 location.state 的 form 赋值给 from, 没有的话就默认 form 是{pathname:"/"}
let { from } = location.state || { from: { pathname: "/" } };
//登录方法 调用定义的fakeAuth的登录方法 回调改变路由地址
//“history.replace 跟 history.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录
let login = () => {
fakeAuth.authenticate(() => {
history.replace(from);
// history.push(from.pathname)
});
};
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
其中我们在受保护组件里面打印一下
history
和location
的值如下
可以看出
location
就是当前URL的location对象 包括路由的属性
CustomRouter(Auth) -- 自定义路由
自定义路由其实最多应用的场景是 应用底部的 tabbar 了吧. 可以根据是否选中改变样式
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch
} from "react-router-dom";
export default class CustomLink extends Component {
render() {
return (
<Router>
<div>
<OldSchoolMenuLink
activeOnlyWhenExact={true}
to="/"
label="Home"
/>
<OldSchoolMenuLink to="/about" label="About" />
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</div>
</Router>
)
}
}
function OldSchoolMenuLink({ label, to, activeOnlyWhenExact }) {
let match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact
});
return (
<div className={match ? "active" : ""}>
{match && "> "}
<Link to={to}>{label}</Link>
</div>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
PreventRouter -- 阻止路由
该组件的使用情景: 输入表单的时候 点击跳转页面后再回来 数据会丢失. 该组件就是用来阻断跳转页面的. 如果点击了取消, 就会还在原页面, 表单数据不会丢失. 如果点击了确定, 会跳转页面. 数据丢失. 其实就是一个二次确认弹窗组件
import React, { Component, useState } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Prompt
} from "react-router-dom";
export default class PreventRouter extends Component {
render() {
return (
<Router>
<ul>
<li>
<Link to="/">From</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Switch>
<Route path="/" exact children={ <BlockingForm /> } />
<Route path="/one" children={<h3>One</h3>} />
<Route path="/two" children={<h3>Two</h3>} />
</Switch>
</Router>
)
}
}
function BlockingForm() {
//定义一个锁 变量 isBlocking 修改的方法是 setIsBlocking 默认值是 false
let [isBlocking, setIsBlocking] = useState(false);
return(
//提交表单 event.preventDefault()默认阻止默认提交事件;
//event.target.reset() 重置表单内容
//setIsBlocking(false) 将 isBlocking 设为 false;
//Prompt 组件阻止过渡 when={isBlocking} 如字面意思 表示当isBlocking为 true 的时候
// message 内容则是 弹窗显示的内容
<form
onSubmit={event => {
event.preventDefault();
event.target.reset();
setIsBlocking(false);
}}
>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<p>
Blocking?{" "}
{isBlocking ? "Yes, click a link or the back button" : "Nope"}
</p>
<p>
<input
size="50"
placeholder="type something to block transitions"
onChange={event => {
setIsBlocking(event.target.value.length > 0);
}}
/>
</p>
<p>
<button>Submit to stop blocking</button>
</p>
</form>
)
}
- Prompt组件的 message属性还可以接收一个函数,该函数可以判断需要跳转的路由,返回true不提示,反之,弹出提示;
<Prompt
when={isBlocking}
message={location =>
location.pathname=="/one"
? true
:
`Are you sure you want to go to ${location.pathname}`
}
/>
这里就是表单数据填好之后点击 Two 弹出提示; 点击 One 则不提示直接跳转
NoMatchRouter(404) -- 未匹配路由
当路由选择到一个未匹配项的时候 会默认选择去
Switch
中的最后一个路由项内. 匹配规则为 *
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect,
useLocation
} from "react-router-dom";
export default class NomatchRouter extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/old-match">Old Match, to be redirected</Link>
</li>
<li>
<Link to="/will-match">Will Match</Link>
</li>
<li>
<Link to="/will-not-match">Will Not Match</Link>
</li>
<li>
<Link to="/also/will/not/match">Also Will Not Match</Link>
</li>
</ul>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/old-match">
<Redirect to="/will-match" />
</Route>
<Route path="/will-match">
<WillMatch />
</Route>
<Route path="*">
<NoMatch />
</Route>
</Switch>
</div>
</Router>
)
}
}
function Home(){
return <h3>Home</h3>
}
function WillMatch(){
return <h3>Matched!!</h3>
}
function NoMatch(){
let location = useLocation();
return (
<div>
<h3>
No match for {location.pathname}
</h3>
</div>
)
}
通常在实际业务中 未匹配项都会跳转到统一一个 404 error 的路由页面. 这里我们将最后一个
Route
换成重定向Redirect
即可
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/old-match">
<Redirect to="/will-match" />
</Route>
<Route path="/will-match">
<WillMatch />
</Route>
<Route path="/error">
<Error />
</Route>
<Redirect from="/*" to="/error" />
</Switch>
RecursiveRouter -- 递归路由
递归路由 简单来讲类似路由嵌套路由. 像一颗递归树 父路由的子路由又有子路由... 逐渐循环下去. 官方给的 demo 是一个 0,1,2,3 的数组对象 每个又有一个friends的子数组,子数组 0,1,2,3 又对应父数组对象的每个对象
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
useParams,
useRouteMatch
} from "react-router-dom";
export default class RecursiveRouter extends Component {
render() {
return (
<Router>
<Switch>
<Route path="/:id">
<Person />
</Route>
<Route path="/">
<Redirect to="/0" />
</Route>
</Switch>
</Router>
)
}
}
function Person(){
let { url } = useRouteMatch();
let { id } = useParams();
let person = find(parseInt(id));
return(
<div>
<h3>{person.name}'s Friends</h3>
<ul>
{
person.friends.map(
id=>
(
<li key={id}>
<Link to={`${url}/${id}`}>{find(id).name}</Link>
</li>
)
)
}
</ul>
<Switch>
<Route path={`${url}/:id`}>
<Person />
</Route>
</Switch>
</div>
)
}
const PEEPS = [
{ id: 0, name: "Michelle", friends: [1, 2, 3] },
{ id: 1, name: "Sean", friends: [0, 3] },
{ id: 2, name: "Kim", friends: [0, 1, 3] },
{ id: 3, name: "David", friends: [1, 2] }
];
function find(id){
return PEEPS.find(p=>p.id===id)
}
写代码的时候写错了一个小 bug ; 就是 这里在写箭头函数的时候
=>
后面习惯性加了{}
导致页面报错: 箭头函数 加括号的函数体返回对象字面量表达式~ 所以这里需要用()包含住
SidebarRouter -- 侧边栏路由
侧边栏是很常见的一种页面展示, 我在这里将官方案列稍微改动了一下 应用了之前的 CustomRouter 的自定义link 方法做了一个小小的点击背景色功能;
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch
} from "react-router-dom";
import "../App.css"
const routes = [
{
path:"/home",
exact:true,
sidebar: ()=><div>home?!</div>,
main: ()=><h2>Home</h2>
},
{
path:"/bubblegum",
sidebar: ()=><div>bubblegum?!</div>,
main: ()=><h2>Bubblegum</h2>
},
{
path:"/shoelaces",
sidebar: ()=><div>shoelaces?!</div>,
main: ()=><h2>Shoelaces</h2>
},
]
export default class SidebarRouter extends Component {
render() {
return (
<Router>
<div style={{display:"flex"}}>
<div style={{padding:"10px",width:"40%",background: "#f0f0f0"}}>
<ActiveLink
activeOnlyWhenExact={false}
to="/home"
label="Home"
/>
<ActiveLink
to="/bubblegum"
label="Bubblegum"
/>
<ActiveLink
to="/shoelaces"
label="Shoelaces"
/>
<Switch>
{
routes.map((route,index)=>(
<Route
key={index}
path={route.path}
exact={route.exact}
children={<route.sidebar />}
/>
))
}
</Switch>
</div>
<div style={{ flex: 1, padding: "10px" }}>
<Switch>
{
routes.map((route,index)=>(
<Route
key={index}
path={route.path}
exact={route.exact}
children={<route.main />}
/>
))
}
</Switch>
</div>
</div>
</Router>
)
}
}
function ActiveLink({ label, to, activeOnlyWhenExact }){
let match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact
});
return(
<div className={match ? "bg_color_blue" : ""}>
{match && "!!"}
<Link to={to}>{label}</Link>
</div>
)
}
css 样式直接引用 App.css 了 里面写了一个
.bg_color_blue{
background-color: skyblue;
}
ConfigRouter -- 配置路由
多数实际应用中我们都是将路由进行集中配置的
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
//定义路由配置
const routes = [
{
path:"/sandwiches",
component: Sandwiches
},
{
path: "/tacos",
component: Tacos,
routes: [
{
path: "/tacos/bus",
component: Bus
},
{
path: "/tacos/cart",
component: Cart
}
]
}
]
export default class ConfigRouter extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/tacos">Tacos</Link>
</li>
<li>
<Link to="/sandwiches">Sandwiches</Link>
</li>
</ul>
<Switch>
{
routes.map((route,i)=>(
<RouteWithSubRoutes key={i} {...route} />
))
}
</Switch>
</div>
</Router>
)
}
}
function RouteWithSubRoutes(route){
return (
<Route
path={route.path}
render={props=>(
<route.component {...props} routes={route.routes} />
)}
/>
)
}
function Sandwiches() {
return <h2>Sandwiches</h2>;
}
// 父组件的 routes 属性
function Tacos({ routes }) {
return (
<div>
<h2>Tacos</h2>
<ul>
<li>
<Link to="/tacos/bus">Bus</Link>
</li>
<li>
<Link to="/tacos/cart">Cart</Link>
</li>
</ul>
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
</Switch>
</div>
);
}
function Bus() {
return <h3>Bus</h3>;
}
function Cart() {
return <h3>Cart</h3>;
}
Route 有一个render和children
render是一个函数,语法:render={()=>{return <div></div>}}
,只要你的路由匹配了,这个函数才会执行
children也是一个函数,不管匹配不匹配,这个函数都会执行 他们两个有个优先级关系,render的优先级总是高于children,是会覆盖children的