前置工作
使用 pnpm i react-router
安装 React Router
编码流程
1.拆分表单与列表
我们将表单和列表拆分成两个页面文件 UserList.tsx
和 UserForm.tsx
tsx
// pages/user/UserList.tsx
import type { User } from "@/types/user";
import { type TableProps, Table } from "antd";
import { useState, useRef, type ChangeEvent } from "react";
import sourceData from "@/db.json";
function UserList() {
//...
return (
<div>
<Table dataSource={filterData} columns={columns} />
{/* 删除掉 Form */}
</div>
);
}
export default UserList;
原来的表单我们直接当作了列表的子组件,现在我们需要将它放置在和列表同级的位置
tsx
// pages/user/UserForm.tsx
import { Form, Input, Select } from "antd";
//...
function UserForm() {
//...
const handleSubmit = (values: UserFormData) => {
// onAddUser({
// ...values,
// age: Number(values.age),
// });
// 添加之后 reset
handleReset();
};
return (
<Form
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
initialValues={defaultFormData}
autoComplete="off"
onFinish={handleSubmit}
>
{/*...*/}
</Form>
);
}
export default UserForm
2. 创建路由
我们参考 React Router 官方文档,选择数据模式来创建我们的路由
tsx
// router/index.tsx
import UserForm from "@/pages/user/UserForm";
import UserList from "@/pages/user/UserList";
import {
createBrowserRouter,
} from "react-router";
const router = createBrowserRouter([
{
path: "/",
children: [
{
path: "user",
children: [
{
path: 'list',
Component: UserList
},
{
path: 'form',
Component: UserForm
}
]
}
]
}
])
export default router
修改根元素为<RouterProvider />
来将页面控制交给路由库,并传入我们创建好的路由
tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router'
import router from './router/index.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
3.使用 Context 和 Reducer 控制状态
我们跳转到 /user/list
和 /user/form
,发现页面渲染是正常的。由于我们还没有引入第三方状态管理库,跨路由管理状态是比较麻烦的,我们用一个比较trick 的方式来解决这个问题,对此我们需要创建一个 Context 从它们的共同祖先上来分发我们的状态到这两个组件中,用 reducer 来简化我们的操作,并且完善两个页面之间的逻辑。我们创建一个 UserLayout.tsx
文件来作为这一层。
tsx
// pages/user/UserLayout.tsx
import { useReducer } from "react";
import { Outlet } from "react-router";
import sourceData from "@/db.json";
import userReducer from "./UserReducer";
import { UserContext, UserDispatchContext } from "./UserContext";
function UserLayout() {
const [users, dispatch] = useReducer(userReducer, sourceData.users);
return (
<UserContext value={users}>
<UserDispatchContext value={dispatch}>
<Outlet />
</UserDispatchContext>
</UserContext>
);
}
export default UserLayout;
创建对应的 context
tsx
// pages/user/UserContext.ts
import type { User, UserAction } from "@/types/user";
import { createContext } from "react";
export const UserDispatchContext = createContext<(action: UserAction) => void>(() => {});
export const UserContext = createContext<User[]>([]);
创建对应的 reducer
tsx
// pages/user/UserReducer.ts
import type { User, UserAction } from "@/types/user";
const userReducer = (users: User[], action:UserAction) => {
switch (action.type) {
case "add": {
return [
...users,
action.payload
]
}
case "delete": {
return users.filter(i => i.userId === action.payload)
}
case "update": {
return users.map(item => {
if (item.userId !== action.payload.id) {
return {
...item,
...action.payload.patch
}
} else {
return item
}
})
}
}
};
export default userReducer
// types/user.ts
export type UserAction =
| { type: "add"; payload: User }
| { type: "delete"; payload: string }
| {
type: "update";
payload: {
id: string;
patch: Partial<User>;
};
};
将创建好的中间层加入到路由表中
tsx
// router/index.tsx
const router = createBrowserRouter([
{
path: "/",
children: [
{
path: "user",
Component: UserLayout,
children: [
//...
],
},
],
},
]);
我们将列表和表单中的状态控制改为使用 context 传递的值,并且添加路由的跳转功能:
- 列表页添加"创建"按钮:点击后跳转到表单页
- 表单页点击"添加"按钮提交表单后:跳转到列表页
tsx
// pages/user/UserList.tsx
import { useContext } from "react";
import { UserContext, UserDispatchContext } from "./UserContext";
import { useNavigate } from "react-router";
function UserList() {
const data = useContext(UserContext)
const dispatch = useContext(UserDispatchContext)
const navigate = useNavigate()
//...
const handleConfirmEdit = (targetId: string) => {
//...
dispatch({
type: 'update',
payload: {
id: targetId,
patch: nextUser
}
})
};
const handleDelete = (targetId: string) => {
dispatch({
type: 'delete',
payload: targetId
})
};
return (
{/*...*/}
<button onClick={() => navigate('../form')}>创建</button>
{/*...*/}
)
}
tsx
// pages/user/UserForm.tsx
import { useContext } from "react";
import { UserDispatchContext } from "./UserContext";
import { useNavigate } from "react-router";
function UserForm() {
//...
const handleSubmit = (values: UserFormData) => {
dispatch({
type: 'add',
payload: {
...values,
age: Number(values.age),
userId: Math.ceil(Math.random() * 100) + ""
}
})
// 添加之后 reset
handleReset();
// 跳转到 list
navigate('../list')
};
//...
}
小结
我们引入了 React Router,通过路由来管理显示的页面,下一步我们探索怎么使用路由参数和管理路由