AI 编程时代手工匠人代码打造 React 项目实战 (三):引入 React Router

前置工作

使用 pnpm i react-router 安装 React Router

编码流程

1.拆分表单与列表

我们将表单和列表拆分成两个页面文件 UserList.tsxUserForm.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 传递的值,并且添加路由的跳转功能:

  1. 列表页添加"创建"按钮:点击后跳转到表单页
  2. 表单页点击"添加"按钮提交表单后:跳转到列表页
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,通过路由来管理显示的页面,下一步我们探索怎么使用路由参数和管理路由

相关推荐
我叫黑大帅3 分钟前
前端单词查询功能是怎么搞出来的?😂
前端·javascript
北京_宏哥6 分钟前
《刚刚问世》系列初窥篇-Java+Playwright自动化测试-33-JavaScript的调用执行-上篇 (详细教程)
java·前端·javascript
张志鹏PHP全栈7 分钟前
Vue3第十三天,Vue3种如何自定义hook
前端·vue.js
Dignity_呱9 分钟前
为什么一定要有微任务,直接一个宏任务不行吗
前端·javascript·面试
Cache技术分享15 分钟前
156. Java Lambda 表达式 - 从 Lambda 到方法引用:深入理解 Java 的未绑定实例方法调用
前端·后端
teeeeeeemo20 分钟前
JS实现数组扁平化
开发语言·前端·javascript·笔记·算法
原则猫20 分钟前
waterfall 妙用
前端
Mintopia21 分钟前
Next.js 组件库搭建指南:Shadcn UI 与 Radix UI 的优雅实践
前端·javascript·next.js
e黑子22 分钟前
GPT 让我效率提升 10 倍,但也差点毁了我的职业生涯
前端·程序员
一滩24 分钟前
使用LLM 实现一个可编辑PPT Agent
前端