使用官方的教学项目
npx create-react-app router-tutorial
安装 react-router 依赖
cd router-tutorial
npm add react-router-dom@6 history@5
react-router-dom是浏览器端的基于react-router库的库,所以装了这个以后就不用再手动装react-router了
修改App.js和 index.js到简单的样子
//src/App.js
export default function App() {
return (
Bookkeeper!
);
}
// src/index.js
import { render } from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
render( , rootElement);
然后启动项目,然后我们可以在基础上修改了。
# probably this
npm start
# or this
npm run dev
#or
yarn start
01.BrowserRouter
连接你的app到浏览器的URL。
用BrowserRouter包裹在你的App的外面
//src/index.js
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const rootElement = document.getElementById("root");
render(
,
rootElement
);
02.添加一些链接Link
在src/App.js
里添加一些链接和全局导航。
import { Link } from "react-router-dom";
export default function App() {
return (
Bookkeeper
);
}
现在点击那些链接,你会发现地址栏会发生改变,也可以用前进后退在历史记录中移动
03.添加一些路由
src/routes/invoices.jsx
src/routes/expenses.jsx
//src/routes/expenses.jsx
export default function Expenses() {
return (
Expenses
);
}
//src/routes/invoices.jsx
export default function Invoices() {
return (
Invoices
);
}
接下来我们需要在index.js里面创建路由配置告诉app如何渲染不同的url
//src/index.js
import { render } from "react-dom";
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
import App from "./App";
import Expenses from "./routes/expenses";
import Invoices from "./routes/invoices";
const rootElement = document.getElementById("root");
render(
} />
} />
} />
,
rootElement
);
04.嵌套路由
我们注意到点击链接的时候,App中的布局消失了。只剩下Expenses或Invoices这两个路由指向的内容
嵌套路由的作用就是共享部分UI
我们需要两步操作实现这一点
首先index.js里面对路由进行嵌套。这样两个组件就变成了App组件的子节点
import { render } from "react-dom";
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
import App from "./App";
import Expenses from "./routes/expenses";
import Invoices from "./routes/invoices";
const rootElement = document.getElementById("root");
render(
}>
} />
} />
,
rootElement
);
当路由拥有子节点的时候会发生两件事
- 路由的url嵌套 (
"/" + "expenses"
and"/" + "invoices"
) - 子路由组件匹配的时候也会渲染父组件共享的部分
接下来我们在App.jsx添加一个Outlet作为渲染子节点路由的地方
//src/App.jsx
import { Outlet, Link } from "react-router-dom";
export default function App() {
return (
Bookkeeper
);
}
这下我们就可以在两个路由间切换保持共享的布局了。
05.给Invoices路由添加数据
我们模拟真实使用场景,给Invoices路由造点假数据
// src/data.js
let invoices = [
{
name: "Santa Monica",
number: 1995,
amount: "$10,800",
due: "12/05/1995"
},
{
name: "Stankonia",
number: 2000,
amount: "$8,000",
due: "10/31/2000"
},
{
name: "Ocean Avenue",
number: 2003,
amount: "$9,500",
due: "07/22/2003"
},
{
name: "Tubthumper",
number: 1997,
amount: "$14,000",
due: "09/01/1997"
},
{
name: "Wide Open Spaces",
number: 1998,
amount: "$4,600",
due: "01/27/2998"
}
];
export function getInvoices() {
return invoices;
}
然后我们修改invoices.jsx组件,获取并且渲染数据
//src/routes/invoices.jsx
import { Link } from "react-router-dom";
import { getInvoices } from "../data";
export default function Invoices() {
let invoices = getInvoices();
return (
);
}
06.添加一个不匹配路由
我们可以发现,当我们输入一个没有分配地址的路由的时候,会显示空白页。
实际上有一个好办法就是把这些不匹配的路由都导入一个404页面。
我们添加一个"*"路由,这个路由会匹配所有没有匹配其他路由的路由
// src/index.js
}>
} />
} />
There's nothing here!
}
/>
07.读取url参数
下面我们添加一些新组件,用于显示固定年份的invoice
// src/routes/invoice.jsx
export default function Invoice() {
return Invoice #???
;
}
然后我们在invoices路由下面添加这个子路由
// src/index.js
}>
} />
}>
} />
There's nothing here!
}
/>
我们刚刚创建的路由是匹配 "/invoices/2005" and "/invoices/1998"这种格式的。
然后我们还需要在invoices.jsx 添加一个outlet,不然显示不出来子路由的内容
然后我们在invoice.jsx 文件中获取url参数
// src/routes/invoice.jsx
import { useParams } from "react-router-dom";
export default function Invoice() {
let params = useParams();
return Invoice: {params.invoiceId}
;
}
接着我们在data.js里面添加一个根据年份返回对应年份数据的函数
//...
export function getInvoices() {
return invoices;
}
export function getInvoice(number) {
return invoices.find(
invoice => invoice.number === number
);
}
然后我们就能用这个函数获取数据并且渲染出来了。
import { useParams } from "react-router-dom";
import { getInvoice } from "../data";
export default function Invoice() {
let params = useParams();
let invoice = getInvoice(parseInt(params.invoiceId, 10));
return (
Total Due: {invoice.amount}
{invoice.name}: {invoice.number}
Due Date: {invoice.due}
);
}
08.index路由
这可能是react router 里面最难理解的概念。
当我们浏览 invoices 路由的子路由内容,之后我们点击invoices路由的链接,我们发现右侧变成了空白。
我们可以添加一个index路由解决这个问题
// src/index.js
}>
} />
}>
Select an invoice
}
/>
} />
There's nothing here!
}
/>
接下来我们发现点击invoices路由的时候会默认显示index路由的内容而不是空白。
index路由和其他路由不同的地方是它没有path属性,他和父路由共享同一个路径。
下面几点可以帮助你理解这个概念
- index路由渲染在父路由的outlet,而且路由地址和父路由相同
- index路由在父路由匹配并且其他子路由不匹配的时候 匹配
- index路由是一个父节点默认的子节点
- index路由在用户还没有点击导航中的链接时渲染
09.高亮激活的链接
通常我们需要,特别是在导航列表里面,需要展示给用户当前激活的链接是哪个。
西面我们把invoices.jsx中的Link换成NavLink
import { NavLink, Outlet } from "react-router-dom";
import { getInvoices } from "../data";
export default function Invoices() {
let invoices = getInvoices();
return (
);
}
我们做了以下3件事
替换Link为NavLink
我们用函数改变样式
我们该百年了链接颜色通过NavLink传递过来的isActive属性
我们也可以利用className做到一样的效果。
// normal string
// function
isActive ? "red" : "blue"} />
10.url搜索参数
搜索参数就类似于url参数,但是他们在url中所处的位置不同。
不是由/
分隔,他们出现在一个?
之后,类似于这样的形式 "/login?success=1"or
"/shoes?brand=nike&sort=asc&sortby=price
react router 提供了 useSearchParams 用于读取和操作搜索参数。它有点像useState,不同点是useState是操作内存中的数据,
而他是设置url 搜索参数中的state
// routes/invoices.jsx
import {
NavLink,
Outlet,
useSearchParams
} from "react-router-dom";
import { getInvoices } from "../data";
export default function Invoices() {
let invoices = getInvoices();
let [searchParams, setSearchParams] = useSearchParams();
return (
);
}
11.自定义行为
接着上一节的程序,
我们发现如果我们点击了过滤出来的链接,就不会保持过滤了,input会被清空。
我们能够在点击新链接的时候保持查询字符串,只要我们组合 Navlink和useLocation,组成一个我们自己的QueryNavLink
import { useLocation, NavLink } from "react-router-dom";
function QueryNavLink({ to, ...props }) {
let location = useLocation();
return ;
}
类似于 useSearchParams
, useLocation
也会返回一个location告诉我们一些信息。就类似于下面的格式
{
pathame: "/invoices",
search: "?filter=sa",
hash: "",
state: null,
key: "ae4cz2j"
}
12.编程式导航
有时我们需要更改URL,
我们添加一个按钮,将invoice删除,然后导航到索引路由。
首先我们在data.js里添加下面用于删除一个invoice的函数
export function deleteInvoice(number) {
invoices = invoices.filter(
invoice => invoice.number !== number
);
}
然后我们添加删除按钮
// src/routes/invoice.jsx
import { useParams, useNavigate } from "react-router-dom";
import { getInvoice, deleteInvoice } from "../data";
export default function Invoice() {
let navigate = useNavigate();
let params = useParams();
let invoice = getInvoice(parseInt(params.invoiceId, 10));
return (
Total Due: {invoice.amount}
{invoice.name}: {invoice.number}
Due Date: {invoice.due}
);
}