项目搭建

1
2
npm create vite@latest nie-react-app -- --template react-ts (一定要加-ts,不然默认用js,后面再改ts很麻烦没成功,我重新创建了一个)
npm install antd react-router-dom @ant-design/icons

目录(初版)

1
2
3
4
5
6
7
8
9
10
11
12
13
src/
├── layouts/ # 布局组件(主布局)
│ └── MainLayout.tsx
├── pages/ # 业务页面
│ ├── Home.tsx # 首页
│ ├── UserList.tsx # 用户列表
│ ├── Settings.tsx # 设置页
│ ├── Login.tsx # 登录页(无布局)
│ └── NotFound.tsx # 404页
├── router/ # 路由配置
│ └── index.tsx
├── App.tsx # 入口组件
└── main.tsx # 渲染入口

main.tsx:

1
2
3
4
5
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

<StrictMode>:React 严格模式(react18开发环境增强工具):一个「无视觉效果的包装组件」,仅在开发环境生效,生产环境会自动忽略

app.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import React from "react";
import { RouterProvider } from "react-router-dom";
import { ConfigProvider } from "antd";
import { router } from "./router";
function App() {
return (
<ConfigProvider>
<RouterProvider router={router} />
</ConfigProvider>
);
}
export default App;

路由配置

router/index.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

import React, { Suspense, type ReactNode } from 'react';
import {createBrowserRouter } from 'react-router-dom';
const Dashboard = React.lazy(() => import('../pages/Dashboard'));
const NieChecklist = React.lazy(() => import("../pages/Checklist"));
interface NieRouteMap {
path: string;
auth: number; //是否需要登录访问
title: string;
key: string; //和path一直
element: any;
hidden?: boolean; //是否在菜单中隐藏
children?: NieChildRouteMap[]; //子路由
}
interface NieChildRouteMap extends NieRouteMap {
parentpath: string; //父级路径。如果是三级菜单,parentpath为/一级路径/二级路径
}
const loadElement = (element: ReactNode):ReactNode => (<Suspense fallback={<div>Loading...</div>}>{element}</Suspense>)
const RouterMap: NieRouteMap[] = [
{
path: "/dashboard",
auth: 0,
title: "Dashboard",
key: "dashboard",
parentpath: "/",
element: loadElement(<Dashboard />),
},{
path: "/checklist",
auth: 0,
title: "CheckList",
key: "checklist",
parentpath: "/",
element: loadElement(<NieChecklist />),
}
];

export const router = createBrowserRouter(RouterMap);

全局布局配置

我希望给整个项目添加一个Header,包含所有页面的导航:

  1. Header组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    Header.tsx:
    const navItems = [
    { path: "/dashboard", title: "Dashboard" },
    { path: "/usermanagement", title: "UserManagement" },
    { path: "/pdftopic", title: "PdfToPic" },
    {
    path: "/documentmanagement",
    title: "DocumentManagement",

    }, {
    path: "/checklist",
    title: "CheckList",
    }
    ];


    const Header = () => {
    const navigate = useNavigate();
    const location = useLocation();

    const handleNavClick = (path: string) => {
    navigate(path);
    };

    return (
    <div style={{ padding: "16px", borderBottom: "1px solid #e8e8e8" }}>
    <Space size="middle">
    {navItems.map((item) => (
    <span
    style = {{
    cursor: "pointer",
    color: location.pathname === item.path ? "#1890ff" : "inherit",
    }}
    key={item.path}
    onClick={() => handleNavClick(item.path)}
    >
    {item.title}
    </span>
    ))}
    </Space>
    </div>
    );
    };
    export default Header;
  2. 使用Layout组件进行全局布局
  3. <Outlet />:路由出口
    来自 react-router-dom 的组件,作用是占位符:
    当访问不同路由(如 /homepage、/usermanagement)时,<Outlet /> 会被对应的页面组件(如 HomePage、UserManagement)替换
    访问 /homepage 时,<Outlet /> 会渲染 HomePage 组件;访问 /usermanagement 时,会渲染 UserManagement 组件
1
2
3
4
5
6
7
8
9
10
11
const RootLayout = () => {
return (
<div style={{ minHeight: "100vh" }}> {/* 占满整个屏幕高度 */}
<Header />
<div style={{ padding: "24px" }}>
<Outlet />
</div>
</div>
);
};
export default RootLayout;
  1. 路由配置
    把Layout组件作为父级路径,希望使用这个布局的页面作为它的children
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const RouterMap: NieRouteMap[] = [
    {
    path: "/",
    auth: 0,
    title: "Roothome",
    key: "roothome",
    element: loadElement(<Layout />),
    children: [
    {
    path: "/homepage",
    auth: 0,
    title: "Homepage",
    key: "homepage",
    parentpath: "/",
    element: loadElement(<HomePage />),
    },{
    path: "/checklist",
    auth: 0,
    title: "CheckList",
    key: "checklist",
    parentpath: "/",
    element: loadElement(<NieChecklist />),
    }
    ]}]
    1

    全局布局配置后页面

store配置(初始版)

  1. 安装依赖
    1
    2
    3
    4
    npm install react-redux redux
    npm install react-redux @reduxjs/toolkit
    页面组件:
    import (useDispatch, useSelector) from "react-redux";
  2. 目录
    1
    2
    3
    4
    5
    src/ 
    ├── store/
    │ └── checklist/
    │ │ └── checklistSlice.ts
    │ └── index.tsx
  3. 创建store
    注意:在Redux Toolkit中,如果我们直接返回一个新的状态,它会替换整个状态。而如果我们不返回,而是修改state,那么Immer会帮我们生成新的状态
    简单新建一个checklistStore,我开始在action里return state,这样做不好,AI帮我更正了代码:
    checklistSlice
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import { createSlice } from '@reduxjs/toolkit'

    const initialState = {
    finallyShowCheckboxList: [],
    basicInformation: { },
    nieChecklistOptions: [],
    }

    const NieChecklistSlice = createSlice({
    name: 'Checklist',
    initialState,
    reducers: {
    initChecklistInfo(state) {
    // 直接重置状态,不需要返回
    state.finallyShowCheckboxList = []
    state.basicInformation = {}
    state.nieChecklistOptions = []
    },
    handleChangeBasicInformationValues(state, { payload }) {
    // 合并更新基本信息
    state.basicInformation = { ...state.basicInformation, ...payload }
    console.log('state.basicInformation:', state.basicInformation)
    // 不需要 return state
    },
    handleChangeBasicInformation(state, { payload }) {
    // 修正:直接替换整个 basicInformation 对象
    state.basicInformation = payload
    // 不需要 return state
    },
    handleChangeFinallyShowCheckboxList(state, { payload }) {
    state.finallyShowCheckboxList = payload
    },
    // 新增:更新选项列表的 reducer
    handleChangeNieChecklistOptions(state, { payload }) {
    state.nieChecklistOptions = payload
    }
    }
    })

    export const {
    initChecklistInfo,
    handleChangeBasicInformationValues,
    handleChangeFinallyShowCheckboxList,
    handleChangeBasicInformation,
    handleChangeNieChecklistOptions
    } = NieChecklistSlice.actions

    export default NieChecklistSlice.reducer

store缓存->浏览器缓存

安装依赖npm install redux react-redux redux-persist
store下的index.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// store/index.js
import { configureStore,combineReducers } from '@reduxjs/toolkit'
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage' // localStorage
import NieChecklistSlice from './Checklist/checklistSlice'
const reducers = combineReducers({
NieChecklistReduce: NieChecklistSlice,
})
// 持久化配置
const persistConfig = {
key: 'checklist', // localStorage 中的 key
storage, // 使用 localStorage
// 可选:指定要持久化的字段
// whitelist: ['finallyShowCheckboxList', 'basicInformation'], // 只持久化这些字段
// blacklist: ['tempData'] // 不持久化这些字段
}

// 创建持久化的 reducer
const persistedReducer = persistReducer(persistConfig, reducers)

// 创建 store
export const store = configureStore({
reducer: {
Checklist: persistedReducer, // 使用持久化的 reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
})

// 创建 persistor
export const persistor = persistStore(store)

本地持久化缓存成功
入口文件main.ts修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { StrictMode } from "react"; // React 严格模式,帮助发现代码问题
import { createRoot } from "react-dom/client"; // 创建 React 应用的根节点
import { Provider } from "react-redux"; // Redux 的提供者,让所有组件都能访问 store
import { PersistGate } from "redux-persist/integration/react"; // 持久化网关,确保数据从存储加载后再渲染
import { store, persistor } from "./store"; // 导入配置好的 store 和持久化对象
import "./index.css"; // 全局样式文件
import App from "./App.tsx"; // 主应用组件

// 创建 React 应用根节点并渲染
createRoot(document.getElementById("root")!).render(
<StrictMode>
{/* 开启严格模式,开发时检测潜在问题 */}
<Provider store={store}>
{/* 提供 Redux store 给所有子组件 */}
<PersistGate loading={null} persistor={persistor}>
{/* 等待数据从存储加载完成 */}
<App /> {/* 你的主应用组件 */}
</PersistGate>
</Provider>
</StrictMode>
);

在组件使用store

最基本导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import {
initChecklistInfo,
handleChangeBasicInformationValues,
handleChangeFinallyShowCheckboxList,
handleChangeBasicInformation,
handleChangeNieChecklistOptions,
} from "../../store/Checklist/checklistSlice";
import NieBasicInformation from "./Components/basicInformation";
import NieHobbyInformation from "./Components/hobbyInformation";
const NieChecklist = () => {
const ChecklistStore = useSelector((state: any) => state.Checklist); //store的name=Checklist
const dispatch = useDispatch();
return <div>
<NieBasicInformation />
<NieHobbyInformation />
</div>;
}
export default NieChecklist;

零碎小册子

  1. 数据存取:页面数据变->放缓存;初次进来->从缓存拿数据放页面
    取数据:整个checklist包含了多个组件,每个组件在缓存里对应一个对象,用useEffect监听缓存具体对象
    store:
    1
    2
    3
    4
    5
    6
    7
    8
    const initialState = {
    allChecklistValue:{
    basicInformation: {},
    hobbyInformation: {},
    },

    nieChecklistOptions: [],
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const MyChecklistStore = useSelector((state: any) => state.Checklist);
    import { useEffect } from "react";
    const basicInfoFormInit = () => {
    if(MyChecklistStore.allChecklistValue.basicInfo){
    basicInfoForm.setFieldsValue(MyChecklistStore.allChecklistValue.basicInfo)
    }
    }
    uesEffect(() => {
    basicInfoFormInit()
    },[MyChecklistStore.allChecklistValue.basicInfo])
  2. 数据判断:取对象里的对象MyChecklistStore.allChecklistValue.basicInfo;判断对象keyif('key' in obj){};判断数组是否有某个值if(arr.includes(value)){}
  3. 如果一个dropdown值会导致其他字段值disable,比如是form.item->在onchange里监听->判断数据变化的key是否为特殊字段的name->form.getFieldValue(name)获取值->判断值是否为特殊值->dispatch(action({key1:’’,,key2:’’,key3:’’}))

ant design组件库应用————实现Form表单联动(checklist)

记录我用到的ant design组件和api

Form表单:

onFieldsChange方法有坑,很容易触发三次,因为他不是单一值变化触发,校验触发也会触发,最好用onValuesChange代替

1
<Form form={basicInfo}></Form>

日期选择器DatePicker:

日期数据格式和转换

DatePicker要求数据格式是dayjs对象,string->dayjs:dayjs(string)
但是缓存存储数据要转换为string,dayjs->string:data.tostring()
自定义日期展示类型:format=”YYYY-MM-DD”

我的代码

存取日期时进行格式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//从页面取日期到store,string->dayjs
const handleBssicFormChange = (changedValues: any, allValues: any) => {
console.log("onValuesChange", changedValues);
const data = changedValues;
if ("birthday" in changedValues) {
const birthdayString = data.birthday.toString();
dispatch(handleChangeBasicInformation({ birthday: birthdayString }));
} else {
dispatch(handleChangeBasicInformationValues(changedValues));
}
};
//从stor取日期到页面,string->dayjs
const initInfoValues = () => {
if (ChecklistStore.NieChecklistReduce?.basicInformation) {
let newObj = ChecklistStore.NieChecklistReduce.basicInformation;
if ("birthday" in newObj) {
newObj = { ...newObj, birthday: dayjs(newObj.birthday) };
}
console.log(newObj);
basicForm.setFieldsValue(newObj);
}
};
useEffect(() => {
initInfoValues();
}, []);

DatePickerA变->DatePickerB自动赋值

form.setFieldsValue({ "birthdayNextYear": nextYearDate });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const handleBssicFormFieldsChange = (changedFields: any, allFields: any) => {
console.log("onFieldsChange", changedFields);
switch (changedFields[0].name[0]) {
case "birthday":
if (changedFields[0].value) {
const birthdayYear = dayjs(
basicForm.getFieldValue("birthday")
).year();
const nextYear = birthdayYear +1;
const nextYearAlso = dayjs(basicForm.getFieldValue("birthday"))
.add(1, "year")
.year();
//nextYear和nextYearAlso结果一样
console.log("birthdayNextYearDate", nextYear, nextYearAlso);
const nextYearDate = dayjs(basicForm.getFieldValue("birthday")).year(
nextYear
);

basicForm.setFieldsValue({ "birthdayNextYear": nextYearDate });
}
break;

}
};

日期限定范围

出生日期不能选则未来
日期限定范围

1
2
3
4
5
<DatePicker
disabledDate={(current) => {
return current && current > dayjs().endOf("day");
}}
/>

form.item的可编辑disable&必填&hidden受其他item值控制:

受checkbox值控制(单一值控制):

受checkbox值控制
Form.item : rules={[{ required: 条件 }]} hidden={条件} label={isFruitCard ? 'Fruit card ' : ' Others'}
组件上: disabled={条件}

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<Row gutter={[32, 16]}>
<Col span={8}>
<Form.Item
name="isLikeNovel"
valuePropName="checked"
rules={[{ required: true }]}
label=" "
>
<Checkbox>如果穿书到月光湿地...</Checkbox>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
shouldUpdate={(preValue, curValue) =>
preValue.isLikeNovel !== curValue.isLikeNovel
}
noStyle //防止双item样式打架
>
{() => (
<Form.Item
label="要给逐日一个大大的拥抱"
name="likeNovel"
rules={[
{ required: basicForm.getFieldValue("isLikeNovel") },
]}
>
<Input disabled={!basicForm.getFieldValue("isLikeNovel")} />
</Form.Item>
)}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
shouldUpdate={(preValue, curValue) =>
preValue.isLikeNovel !== curValue.isLikeNovel
}
noStyle
>
{() => (
<Form.Item
label="要让雾刃喝下错误季节"
name="likeAuthor"
rules={[
{ required: basicForm.getFieldValue("isLikeNovel") },
]}
>
<Input disabled={!basicForm.getFieldValue("isLikeNovel")} />
</Form.Item>
)}
</Form.Item>
</Col>
</Row>

受Select值控制(多个值控制):

一个Select组件的多个选项都可以值另一个item变为必填:
一定要有||[],不然初次渲染会报错,或者在.getFieldValue(‘favoriteFruit’)前加问号

1
2
3
4
<Form.Item
rules={[{required: ['apple','orange','watermelon','banana'].some((item)=>(basicForm.getFieldValue('favoriteFruit')||[]).includes(item))
}]}
>

<Form.Item

Checkbox:(message组件静态方法使用失败)

三个Checkbox有一个选中则弹消息(message组件静态方法使用失败)

  1. 完整代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const handleBssicFormChange = (changedValues: any, allValues: any) => {
    ...
    //三个checkbox选中其一则弹提示消息
    const checkboxFields = [
    "sleepBeforTwelve",
    "drinkWater",
    "eatApple"
    ];
    //判断changeValues中是否有checkbox字段
    const isCheckboxFieldsChange = checkboxFields.some((field) =>
    Object.hasOwn(changedValues, field)
    );

    if (isCheckboxFieldsChange) {
    //再判断是选中还是取消选中
    let ifCheckboxTrue = Object.values(changedValues).some(value => value === true)
    console.log("ifCheckboxTrue", ifCheckboxTrue);
    if (ifCheckboxTrue) {
    console.log('进来了')
    setTimeout(() => {
    message.warning("继续保持哦");
    }, 100);
    }
    }

    };
  2. message组件静态方法使用失败:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //我开始的代码,能走进来但是始终不会触发消息弹窗,奇怪的是公司代码我这样写就可以
    const handleBssicFormChange = (changedValues: any, allValues: any) => {
    ...
    if (isCheckboxFieldsChange) {
    //再判断是选中还是取消选中
    let ifCheckboxTrue = Object.values(changedValues).some(value => value === true)
    console.log("ifCheckboxTrue", ifCheckboxTrue);
    if (ifCheckboxTrue) {
    message.warning("继续保持哦");
    }
    }
    }
    原因:React18的兼容性与调用时机,应将message的调用放在事件处理函数(如onClick)、生命周期钩子或useEffect中
    然后我这样改,依然报错:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const handleBssicFormChange = (changedValues: any, allValues: any) => {
    ...
    if (isCheckboxFieldsChange) {
    //再判断是选中还是取消选中
    let ifCheckboxTrue = Object.values(changedValues).some(value => value === true)
    console.log("ifCheckboxTrue", ifCheckboxTrue);

    useEffect(() => {
    message.warning("继续保持哦");
    }, [ifCheckboxTrue === true]);
    }

    }
    原因:在普通的JavaScript函数中使用了React Hook(useEffect),这是不允许的。React Hooks只能在函数组件的最顶层或其他Hooks中调用
    我还是觉得我最初的代码没问题,AI让我加延迟,还是没用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if (isCheckboxFieldsChange) {
    //再判断是选中还是取消选中
    let ifCheckboxTrue = Object.values(changedValues).some(value => value === true)
    console.log("ifCheckboxTrue", ifCheckboxTrue);
    if (ifCheckboxTrue) {
    console.log('进来了')
    setTimeout(() => {
    message.warning("继续保持哦");
    }, 100);
    }
    }

Select多个选项中有三个选项只可选其一

Select组件选择的选项是一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const [fruitTypeOptions,setFruitTypeOptions] = useState(originalFruitOptions)
<Select
mode="tags"
options = {fruitTypeOptions}
onChange={(values)=>{
//三个互斥选项的值
const exclusiveOptions = [
'apple',
'orange','watermelon'
]
//从已选择的选项中找出涉及exclusiveOptions数组选项的值
const obj = values.find((value)=>exclusiveOptions.includes(value))
//如果已选的包含互斥选项
if(obj){
setFruitTypeOptions(originalFruitOptions.map((option:any)=>{
//如果原始选项中某一option的值不等于已选的互斥obj,同时这个option的值属于三个互斥项之一,那么此时要把他变成不可选
if(option.value === obj){
return {...option,disabled:true}
}
//其他情况依旧可选
return {...option,disabled:false}
}))
}
else{
setFruitTypeOptions(originalFruitOptions)
}
}}
>

条件判断

  1. checkbox勾选->控制其他input的disable
    checkbox

    官方文档:
    Form.Item 默认绑定值属性到 value 上,而 Switch、Checkbox 等组件的值属性为 checked。你可以通过 valuePropName 来修改绑定的值属性
    <Form.Item label=" " name="isLikeNovel" valuePropName="checked">

组件库应用–可编辑Table(checklist)

相关ts语法

type CompanyTableColumnTypes = Exclude<TableProps<NBChecklistCoverageAgeTableDataType>['columns'], undefined>
从 Ant Design 的 Table 组件的 columns 属性中,提取出 “非 undefined 的列配置类型”,用于确保你定义的列不会是 undefined
TableProps
一个泛型类型,一个 Table 组件的所有属性(props)类型,其中 NBChecklistCoverageAgeTableDataType 是你表格数据的类型(比如每行的数据结构)
TableProps<...>['columns']
取出 TableProps 中的 columns 字段,在 Ant Design 中,columns 是一个可选的数组,类型通常是: ColumnProps<NBChecklistCoverageAgeTableDataType>[] | undefined
Exclude<T, U> `是 TypeScript 的一个内置工具类型,从类型 T 中剔除 U 类型 所以: Exclude<ColumnProps<...>[] | undefined, undefined>
→ 等价于: ColumnProps<...>[]
把 undefined 剔除后,只剩下数组类型
所以最终 CompanyTableColumnTypes 就是 一个合法的、非 undefined 的表格列配置数组类型
为什么这样做?防止你在定义列时,不小心写成 undefined。确保你传给 Table 的 columns 是一个真正的列数组,而不是 undefined。提高类型安全性,避免运行时的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const coverageAgeTableColumns: (CoverageAgeTableColumnTypes[number] & {
editable?: boolean;
dataIndex: string;
})[] = [
{
title: "Product Type",
dataIndex: "productType",
key: "productType",
},
{
title: "Maximum Entry Age",
dataIndex: "maxEntryAge",
key: "maxEntryAge",
editable: coverageInformationForm.getFieldValue("ageLimitType") === "02",
},
{
title: "Maximum Cover Age",
dataIndex: "maxCoverAge",
key: "maxCoverAge",
editable: coverageInformationForm.getFieldValue("ageLimitType") === "02",
}
];

CoverageAgeTableColumnTypes[number]CoverageAgeTableColumnTypes 是一个联合类型(通常是 ColumnProps<…>[] 的类型推断)。number 索引表示:从这个联合类型中取出每一个可能的列类型。作用:确保你定义的每一列,都符合预设的列结构之一。
举例:

1
2
3
4
type CoverageAgeTableColumnTypes = [
{ title: string; dataIndex: 'productType'; key: 'productType' },
{ title: string; dataIndex: 'maxEntryAge'; key: 'maxEntryAge'; editable?: boolean },
];

那么 CoverageAgeTableColumnTypes[number] 就是:

1
2
{ title: string; dataIndex: 'productType'; key: 'productType' } |
{ title: string; dataIndex: 'maxEntryAge'; key: 'maxEntryAge'; editable?: boolean }

& { editable?: boolean; dataIndex: string }强制所有列都必须有 dataIndex,且可选支持 editable。

完整的可编辑Tables

条件渲染

1
{ value ? <div>sth</div> : null}

响应拦截器–节流防抖

ant design草稿本

Form

  1. 通过Form.useForm() 创建并绑定表单实例,作用是提供对表单的直接控制能力,表单实例提供了一系列方法,让你可以主动读取、设置或清空表单数据,而无需通过 onChange 手动维护状态

    1
    <Form baiscform={form}></Form>
  2. form两个重要监听方法
    form两个重要监听方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    form.getFieldsValue()  //返回表单所有数据
    form.getFieldsValue(['name1','name2']) //返回[[name1,key1],[name2,key2]]
    const handleChangeForm = (changedFields,allFields)=>{
    switch(changedFields[0].name[0]){
    case 'checkbox':
    if(changedFields[0].value){
    dispatch(handleChangeSelectboxList[...selectedCheckoxList,changedFields[0].name[0]])
    }
    break
    defult:
    break
    }
    }
    <Form nform={form} onFieldsChange={handleChangeForm}></Form>

    onValuesChange 监听表单值的变化(最终数据),onFieldsChange 监听表单字段状态的变化(字段信息)

    1. onValuesChange:监听表单值变化
      触发时机:表单字段的 value 发生改变时触发(如输入框输入文字、复选框勾选 / 取消)。
      参数说明:
      changedValues:仅包含本次变化的字段键值对(如 { name: “张三” })。
      allValues:表单当前所有字段的完整值(如 { name: “张三”, age: 20, isLikeNovel: true })。
      使用场景:
      需根据字段值更新状态(如同步表单值到 Redux Store)。
      基于值的简单联动(如勾选 isLikeNovel 后显示相关输入框)。
      示例:你的代码中同步表单值到 basicInformation,仅需关注变化的字段,无需处理字段元信息。
    2. onFieldsChange:监听表单字段状态变化 值变化的同时需要对name进行校验用这个方法
      触发时机:表单字段的值或状态发生改变时触发(值变化、禁用状态切换、校验状态变化等)。
      参数说明:
      changedFields:本次变化的字段数组,每个元素包含字段的 name、value、errors、warnings、disabled 等元信息(如 [{ name: “character”, value: “INFP”, disabled: false }])。
      allFields:所有字段的完整元信息数组
  3. form.item重新渲染
    form.item是受控组件,它的渲染逻辑以来form的状态
    如果form.itemA的受到form.itemB的影响:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <Col span={8}>
    <Form.Item
    shouldUpdate={(preValue, curValue) =>
    preValue.isLikeNovel !== curValue.isLikeNovel
    }
    noStyle
    >
    {() => (
    <Form.Item label="最喜欢的小说" name="likeNovel">
    <Input disabled={!basicForm.getFieldValue("isLikeNovel")} />
    </Form.Item>
    )}
    </Form.Item>
    </Col>
  4. labelInValue
    把每个选项的label包装到 value 中,会把 Select 的 value 类型从 string 变为 {value: string, label: ReactNode} 的格式
    默认情况下 onChange 里只能拿到 value,如果需要拿到选中的节点文本 label,可以使用 labelInValue 属性,如果没有它,选中之后 onChange 捕获的 value 没有值

Table

  1. filteredValue 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 string []
  2. pagination 分页器配置项
    • const navigate = useNavigate()
      • 是 React Router 中的一个 Hook,用于在 React 应用中实现编程式导航。编程式导航允许你在代码中控制导航行为,而不是通过点击链接或表单提交来触发导航。
  1. Table–scroll滚动条配置
  • scrollToFirstRowOnChange属性:当分页、排序、筛选变化后是否滚动到表格顶部(boolean)
  • 控制Table是否可选 rowSelection={{type: 'checkbox'|'raido'}}
    在定义表格列时,如果需要扩展功能(如添加 “编辑”、”查看” 按钮),可以在 render 函数中返回包含多个按钮的容器(如 Ant Design 的 Space 组件)

控制Table是否可选 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  // 表格列配置
const columns = [
{
title: "User Code", // 列标题
dataIndex: "userCode", // 对应数据字段名
key: "userCode", // React需要的唯一key
},
{
title: "User Name",
dataIndex: "userName",
key: "userName",
},
{
title: "Email",
dataIndex: "email",
key: "email",
},
{
title: "Action", // 操作列
key: "action",
// 自定义渲染函数
// _: 当前行数据(未使用)
// record: 当前行记录对象
render: (_, record) => (
<Button
danger // 危险按钮样式红色
onClick={() => handleDelete(record.key)} // 点击删除
>
Delete
</Button>
),
},
];

<Table
rowSelection={{type: 'checkbox'|'raido'}} />

InputNumber 数字输入框

  1. formatter展示值规则
    formatter={dataIndex === ‘claimPaid’ ? Aformatter : Bformatter}
  2. parser存值规则(存储的数据,要传给后端的数据的转换规则)
    parser={dataIndex === ‘claimPaid’ ? Aparser : Bparser} // 存值规则(存储的数据,要传给后端的数据的转换规则)
    />

Upload

onChange 事件:上传中、完成、失败都会调用这个函数
当删除时候,当前文件的状态改变,也会触发它
它的两个参数 file 和 fileList
file 是当前操作文件的详细信息,当文件被删除时,操作的是被删除文件,所以 list 的数据是被删除文件对象
fileList 是当前文件列表数组,删除当前上传 / 预上传的文件 ->fileList 会对应删除文件

时间选择器

  1. allowClear 属性用于控制日期选择器是否显示清除按钮。
    当设置为 true 时,会自动添加一个默认的清除按钮。
    当设置为一个对象时,可以通过 clearIcon 属性来自定义清除按钮的图标
  2. disabledDate 不可选择的日期
  3. shouldUpdate 属性用于控制表单项在值更新时的行为,通过比较更新前后的表单值来决定是否更新
    <Form.Item shouldUpdate={(prevValues, curValues) => prevValues.updatedYear === curValues.updatedYear}>
    updatedYear 的值发生变化时,表单项会更新
    当 shouldUpdate 为 true 时,Form 的任意变化都会使该 Form.Item 重新渲染。这对于自定义渲染一些区域十分有帮助,要注意 Form.Item 里包裹的子组件必须由函数返回,否则 shouldUpdate 不会起作用当 shouldUpdate 为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新
    updatedYear: dayjs().year().toString(): 获取当前日期和时间的年份转字符串onFinish 属性:提交表单且数据验证成功后回调事件onFieldsChange 属性:字段更新时触发回调事件
    1
    2
    3
    <Form.Item label="Updated Year" name="updatedYear">
    <Select allowClear options={updatedYearOptions} />
    </Form.Item>
  4. 转换:dayjs=->string string->dayjs

树形控件

React Hook

useEffect 副作用与依赖类型

在函数组件中执行副作用操作

1
2
3
4
5
6
7
import { useEffect } from 'react';
useEffect(() => {
// 副作用逻辑
return () => {
// 清理逻辑(可选)
};
}, [dependencyArray]); // 依赖项数组(可选

依赖项数组 [] 的三种情况:

  • 不提供:Effect 在每次渲染后都会执行。
  • 空数组 []:Effect 只在组件挂载后执行一次
  • 有依赖的数组 [a, b]:Effect 在组件挂载后和 a 或 b 发生变化后执行
  • 整个缓存:缓存的引用变化时触发
    清理函数用于避免内存泄漏(如取消订阅、清除定时器)

useState 状态管理

在函数组件内部管理局部状态,是React 数据流的核心,状态的更新会触发组件的重新渲染

1
2
3
import { useState } from 'react';
const [count, setCount] = useState<number>(0);
setCount(3);

状态更新可能是异步的,React会对多次setState 进行批处理以提高性能
如果新状态与旧状态相同(通过 Object.is 比较),React 将跳过这次渲染,是一种优化

useRef 值存储

是 React 提供的一个钩子,用于在函数组件中创建对 DOM 元素或普通 JavaScript 值的引用。这个引用在组件的整个生命周期内保持不变
存储值(不引发组件重新渲染):useRef 可以存储一个值,在更新时,该值不会触发组件重新渲染,且该值可以在组件的整个生命周期中保存
实现可变的 “数据容器”:useRef 内部使用的是一个 “可变的容器”,这个容器不会随着组件更新重新初始化
useRef 的核心特性
useRef 返回一个具有 current 属性的对象:{current: initialValue}
current 的值可以任意修改,但修改不会触发组件重新渲染
生命周期内保持稳定的引用:useRef 创建的引用在整个组件生命周期中保持不变
它既不会随着组件更新丢失,也不会重新初始化
更新 current 属性的值不会让组件重新渲染,而使用 useState 更新状态的值会导致重新渲染
注意:useRef 不应用于需要自动触发渲染的数据存储。对于需要反映到 UI 的数据,应该使用 useState

useLocation

useLocation () 是 React Router 中的一个 Hook,用于获取当前的路由位置对象
这个对象包含以下属性:
pathname:当前路径的名称
search:URL 中的查询参数部分
hash:URL 中的哈希部分
key:代表此次导航
state:传递给当前路径的状态数据,里面的数据是路由跳转时 state 传值来的

eg1

1
2
3
4
5
6
7
8
9
10
//跳转传参
navigate('/melon', { state: { error: err } })
// 接收
import { useLocation } from 'react-router-dom'
const errorMessage = useLocation().state?.error;
<Result
status="500"
title="error"
subTitle={`${errorMessage}`}
/>

eg2

1
2
3
navigate('/melon', {
state: {activeKey: 'melon', activeTab: 'MEDICAL', from, melonLink,}
})
1
2
3
4
5
6
const hyperLink = useLocation().state?.melonLink;
<div className={classNames(style['children-content-container'])}>
{
activeTab === 'MELON' ? <childrenModule melonLink={melonLink} from={from} ref={lifeRef} />
}
</div>

useImperativeHandle 子传父

让父组件能够调用子组件的特定方法,而不是直接访问子组件的整个实例
子组件

1
2
3
4
5
6
7
useImperativeHandle(
ref,
() => ({
handleSaveChecklistDetailInformation,
}),
[checklistDetailInformationForm] // 触发时机,希望form内容修改时,暴露最新的方法出去
);

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const checklistRef = useRef(null)
//子组件使用这里要ref={checklistRef}
{
key: 'melon',
label: 'melon',
children: <Checklist ref={checklistRef}>
}

try {
await checklistRef.current.handleSaveChecklistDetailInformation()
} catch (error) {
flag = false
return
}

useMemo & useCallback

缓存函数和计算结果,避免不必要的重新创建和重渲染

useRef DOM引用与可变值0容器

可以存放任何会变化的值,但其值的改变不会触发组件重新渲染

1
2
3
import { useRef, useEffect } from 'react';
const refContainer = useRef(initialValue);
// 通过 refContainer.current 访问当前值

useContext 跨组件数据传递

useReducer

1
2
3
4
5
6
7
8
9
10
11
12
// 在全局作用域定义
let lastShowErrorTime429 = 0
const delayTime = 5000
if (error.response.status === 429){
const nowTime = Date.now();
const diffTime = nowTime - lastShowErrorTime429
if(diffTime >= delayTime){
lastShowErrorTime429 = nowTime
message.error(error.response.data?.message)
}
return Promise.reject()
}
1
2
3
4
5
6
7
8
9
10
// 在全局作用域定义
const delayTime = 2000
let timerShowError429 = null
if (error.response.status === 429){
clearTimeout(timerShowError429)
timerShowError429 = setTimeout(()=>{
message.error(error.response.data?.message)
},delayTime)
return Promise.reject()
}

节流