# Antd常见问题
# Form
默认是通过htmlType="submit" 来触发表单提交
如果我不需要htmlType="submit",而是自定义按钮来提交
<Button
onClick={() => {
// form.validateFields()
// 或者
myRef.current?.validateFields()
}}
/>
2
3
4
5
6
7
# Form设置trigger触发时间
在ElementUI表单校验中,可以设置trigger属性来实现触发时机
在Antd中,通过设置Form.Item的validateTrigger属性来实现
rules也有validateTrigger属性,但是官方描述中 设置触发验证时机,必须是 Form.Item 的 validateTrigger 的子集
表明Form.Item中的validateTrigger是必填的
输入时就校验表单
修改validateTrigger 为 "onChange"
Form.Item的validateTrigger默认为onChange,所以修改rule的validateTrigger为onChange时可以省略Form.Item的不用写
<Form.Item
label='Username'
name='username'
rules={[
{ required: true, message: 'Please input your username!' },
{
validator: (rule, value, callback) => {
console.log(value)
if (Number(value) < 100) {
return Promise.reject('must > 100')
}
},
validateTrigger: 'onChange'
}
]}
>
<Input />
</Form.Item>
// 或者
<Form.Item
label="Username"
name="username"
validateTrigger={["onChange", "onBlur"]}
rules={[
{ required: true, message: "Please input your username!" },
{
validator: (rule, value, callback) => {
console.log(value);
if (Number(value) < 100) {
return Promise.reject("must > 100");
}
return Promise.resolve();
},
validateTrigger: "onBlur",
},
]}
>
<Input />
</Form.Item>;
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
# Form.Item给Switch绑定字段
# Form.Item valuePropName
当Item内部是Switch时,校验无法触发,这时需要设置Item的valuePropName属性为checked
valuePropName该属性为 getValueProps 的封装,自定义 getValueProps 后会失效
# getValueFromEvent
设置如何将 event 的值转换成字段值
<ProFormCheckbox
label="支持ASM"
name={"asm"}
getValueFromEvent={(e: CheckboxChangeEvent) => {
return e.target.checked ? 1 : 0;
}}
/>;
2
3
4
5
6
7
# Antd Form Rules
Antd Form表单使用的也是async validator
Ant Design Pro 中的 Form 组件的 asyncValidator 属性已经不再支持使用回调(callback)函数的方式,而是要求使用 Promise 来处理异步校验。
一般我们可以通过返回一个 Promise 对象来实现异步校验,例如:
const validateString = async (rule, value) => {
const pattern = /\s/;
if (!value) {
return Promise.reject('输入不能为空!');
}
if (pattern.test(value)) {
return Promise.reject('输入不能包含空格,请删除空格后再试!');
}
return Promise.resolve();
}
2
3
4
5
6
7
8
9
10
# Antd的Form.Item内的Input不能实现真正意义上的双向绑定
本来Form.Item内的Input组件是如下实现双向绑定的
<Input
value={formState.username}
onChange={(e) => setFormState({ ...formState, username: e.target.value })}
/>
2
3
4
我在无意中给表单设置rules时发现的,如果不给Item设置name属性,rules将不会生效
而一旦设置了name属性后,Item内的Input就会失去双向绑定(可以试着修改数据,但是UI不会改变)
因此,Antd Form 设置name绑定后,表单就变成非受控表单了
<Form.Item
label='Username'
name='username'
rules={[
{ required: true, message: 'Please input your username!' },
{
validator: (rule, value, callback) => {
console.log(value)
if (Number(value) < 100) {
return Promise.reject('must > 100')
}
return Promise.resolve()
},
validateTrigger: 'onBlur'
}
]}
>
<Input
value={formState.username}
onChange={(e) => setFormState({ ...formState, username: e.target.value })}
/>
</Form.Item>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在antd的表单组件中。如果给每个item组件设置了name。那么就不需要去手动实现双向绑定了。只需要通过 form.setFieldsValue方法。将对应的数据传过去。from组件就会根据对应的name和字段名来双向绑定。
# 如果我需要Input和我定义的值绑定,并且能够表单校验
方法一:使用 getFieldDecorator
方法二:使用 useForm 钩子
// 当 data 发生变化时,更新表单字段的值
useEffect(() => {
form.setFieldsValue(data);
}, [data]);
2
3
4
这样Item的name属性就可以和Input的value属性一起使用了
# Antd的Form是非受控表单
不过也支持受控表单
使用getFieldDecorator,但是getFieldDecorator在Antd4中已被废弃
使用useForm hooks,对于非受控表单,可以使用
getFieldsValue或setFieldsValue方法来操作表单字段的值
Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题。因而你无法在 render 阶段通过
form.getFieldsValue来实时获取字段值,而useWatch提供了一种特定字段访问的方式,从而使得在当前组件中可以直接消费字段的值。
如果想要实时获取表单字段
const { useForm, useWatch } = Form
function App() {
const [form] = useForm<FormState>()
const age = useWatch('age', form)
return (
{age && age > 20 && <div>ag > 20</div>}
)
}
2
3
4
5
6
7
8
9
同时,如果为了更好的渲染性能,你可以通过 Field 的 renderProps 仅更新需要更新的部分。
const MyForm = () => {
const onFinish = (values) => {
console.log('表单提交的数据:', values);
};
return (
<Form onFinish={onFinish}>
<Form.Item
name="name"
label="姓名"
rules={[
{
required: true,
message: '请输入姓名',
},
]}
>
<Field name="name">
{({ field }) => (
// 这里打印 field:{value: 'zs33123233', onChange: ƒ}
<Input {...field}
placeholder="请输入姓名"
onChange={(e) => {
// 在这里自定义处理字段值的逻辑
const newValue = e.target.value;
// ...
field.onChange(e); // 触发字段值的更新
}}
/>
)}
</Field>
</Form.Item>
</Form>
);
};
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
# Form.Item如何绑定一个对象的属性
你可以使用对象的属性路径来绑定对应的值。
在 Form.Item 组件的 name 属性中使用了一个数组 ['user', 'name'] 和 ['user', 'email'] 来表示对象属性的路径。
<Form initialValues={initialValues} onFinish={handleSubmit}>
<Form.Item label="Name" name={['user', 'name']}>
<Input />
</Form.Item>
<Form.Item label="Email" name={['user', 'email']}>
<Input />
</Form.Item>
<button type="submit">Submit</button>
</Form>
2
3
4
5
6
7
8
9
# form.submit()和form.validateFields()
https://github.com/fncheng/react-learn/issues/10
form.submit(): 这个方法用于手动提交表单。当你调用form.submit()时,它会触发表单的提交操作,即执行 onFinish 回调函数。在 onFinish 回调函数中,你可以获取到表单的所有值,并进行相应的处理。form.validateFields(): 这个方法用于手动触发表单字段的校验。当你调用form.validateFields()时,它会对表单中的所有字段进行校验,然后返回一个 Promise 对象。你可以通过.then()方法来处理校验结果,或者使用 async/await 来等待校验完成。
# form.resetFields
重置一组字段到 initialValues
# form.getFieldValue
Antd的form.getFieldValue和form.getFieldsValue是实时的,但是不会 导致页面更新,因此不是响应式的
# Antd Form labelCol和wrapperCol对齐
控制label和field输入区域对齐的参数
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}></Form>
这么设置可以让表单的label和输入区域对齐

# Antd Modal 确定按钮可以快速点击多次
Antd Modal 的确定按钮在默认情况下确实允许快速重复点击,这可能会导致用户重复提交数据
可以给footer部分按钮添加disabled属性
<Modal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
footer={
<Button onClick={handleSubmit} disabled={isSubmitting}>
确定
</Button>
}
>
Modal 内容
</Modal>
2
3
4
5
6
7
8
9
10
11
# Table
# Table定义类型
import { Table, TableColumnType } from "antd";
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const columns: TableColumnType<DataType>[] = [
{ dataIndex: "name", title: "姓名" },
{ dataIndex: "age", title: "年龄" }
];
2
3
4
5
6
7
8
9
10
11
12
13
或者使用ColumnsType
import type { ColumnsType } from "antd/es/table";
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const columns: ColumnsType<DataType> = [
{ dataIndex: "name", title: "姓名" },
{ dataIndex: "age", title: "年龄" }
];
2
3
4
5
6
7
8
9
10
11
12
13
# Antd Table sort触发请求
在Antd Table中,要实现点击表头排序后触发请求的功能,你可以使用Table组件的onChange属性和排序相关的state来实现。
首先,你需要在父组件中维护一个sorter和sortOrder的状态,并将它们传递给Table组件的onChange属性。
import { Table } from 'antd';
class MyTable extends React.Component {
state = {
data: [...], // 表格数据
sorter: {
field: '',
order: ''
}
};
handleTableChange = (pagination, filters, sorter) => {
// 更新排序状态
this.setState({ sorter });
// 发起请求,根据排序状态获取数据
this.fetchData(sorter.field, sorter.order);
};
fetchData = (field, order) => {
// 发起请求,获取数据
// 根据field和order参数进行排序,返回排序后的数据
// 更新state中的data状态
};
render() {
const { data, sorter } = this.state;
const columns = [
{ title: 'ID', dataIndex: 'id', sorter: true },
{ title: 'Name', dataIndex: 'name', sorter: true },
// ...
];
return (
<Table
columns={columns}
dataSource={data}
onChange={this.handleTableChange}
sorter={sorter}
/>
);
}
}
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
# Table filterDropdown自定义筛选
filterDropdown属性是一个函数
filterDropdown: ({ selectedKeys, setSelectedKeys, confirm, close }) => React.ReactNode
其中有个参数confirm用于通知Table组件用户完成了筛选操作(这里的完成指的是用户已经输入的关键词并点击搜索),筛选后的数据需要触发相应回调函数
当调用confirm后会发生什么
当调用 confirm 方法后,会触发 Table 组件的筛选操作。
- 根据当前列的筛选条件,Table 组件会更新相应列的筛选值,并根据新的筛选值重新渲染表格。
- Table 组件会将最新的筛选值传递给
onChange回调函数的filters参数,以便你可以获取最新的筛选数据并进行处理。 - 如果你在
onChange回调函数中执行了一些与筛选相关的操作,例如过滤数据、重新请求接口等,这些操作将会在调用confirm方法后被触发。
查看filterDropdown源码
const propFilteredKeys = filterState?.filteredKeys;
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
// ....
dropdownContent = column.filterDropdown({
prefixCls: `${dropdownPrefixCls}-custom`,
setSelectedKeys: (selectedKeys: Key[]) => onSelectKeys({ selectedKeys }),
selectedKeys: getFilteredKeysSync(),
confirm: doFilter,
clearFilters: onReset,
filters: column.filters,
visible: mergedVisible,
close: () => {
triggerVisible(false);
},
});
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
if (closeDropdown) {
triggerVisible(false);
}
internalTriggerFilter(getFilteredKeysSync());
};
// ======================= Submit ========================
const internalTriggerFilter = (keys?: Key[]) => {
const mergedKeys = keys && keys.length ? keys : null;
if (mergedKeys === null && (!filterState || !filterState.filteredKeys)) {
return null;
}
if (isEqual(mergedKeys, filterState?.filteredKeys, true)) {
return null;
}
triggerFilter({
column,
key: columnKey,
filteredKeys: mergedKeys,
});
};
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
confirm会触发doFilter,而doFilter会触发internalTriggerFilter(getFilteredKeysSync()),其中getFilteredKeysSync()用于获取搜索关键词,该函数返回一个string[]
# Antd Table设置单元格点击事件
通过onCell属性设置
const columns: ProColumns<DrsApplicationInfo>[] = [
{
title: '备注',
dataIndex: 'remark',
width: 200,
onCell: (record) => ({
onDoubleClick: () => {
setEditingKey(record.uuid);
},
}),
]
2
3
4
5
6
7
8
9
10
11
# 设置单元格可编辑
第一种思路是给表格每行数据RowData加一个editing属性,用于控制是否处于编辑状态,这样做会导致可以同时编辑多行数据,如果只能编辑一行数据需要做额外处理。
第二种思路是设置一个key,当要编辑的行id === key时,该行进入编辑状态
https://codesandbox.io/s/antd-table-dan-yuan-ge-ke-bian-ji-lyr4mm
# Antd Table fixed后错位
错位问题实际上是表头最右侧多出了一个th元素导致的
当给Table设置scroll: x: '100%', y: '100%'时反而不会出现错位
经测试设置overflow-y: scroll 可以解决错位问题并且保持表格原貌
给表格设置内部滚动条并解决错位问题
/**
* 给ProTable的body部分设置滚动条
* @param {number} options.extraHeight 表格body底部到可视区域的距离(默认为 70)
* @param {RefObject} options.ref ProTable组件ref
* @returns {string} 返回表格的垂直滚动高度
*/
export const getTableScrollY = ({
extraHeight = 70,
ref,
}: {
extraHeight?: number;
ref: RefObject<HTMLDivElement>;
}) => {
const tSearch: HTMLElement | null | undefined = ref.current?.querySelector(
'div.ant-pro-table-search',
);
const tBody = ref.current?.querySelector('div.ant-table-body');
if (tSearch) {
tSearch.style.marginBottom = '10px';
}
/**
* scrollElement 用于解决表格fixed时出现错位的问题
* 给表格y轴设置overflow-y: scroll时就不会出现错位
*/
const scrollElement: HTMLElement | null | undefined = ref.current?.querySelector(
'.ant-table-cell-fix-right.ant-table-cell-fix-right-first',
);
if (tBody) {
const { top }: DOMRect = tBody!.getBoundingClientRect();
const height = `calc(100vh - ${top + extraHeight}px)`;
const oldStyle = tBody?.getAttribute('style');
const newStyle = `min-height: ${height};overflow-y: ${scrollElement ? 'scroll' : 'auto'}`;
tBody?.setAttribute('style', `${oldStyle} ${newStyle}`);
return height;
} else throw new Error('ProTable必须设置scroll属性');
};
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
具体代码封装见issues/27#issuecomment-1237690093 (opens new window)
使用这个方法后,如果从一个页面滚动到底部,然后跳转到另一个页面,会出现底部大部分空白的情况。
这是因为浏览器在页面之间的跳转中通常会保留滚动的状态,跳转到另一个页面时,视口的距离会继承第一个页面的视口距离,导致该方法计算出现问题。
# 进一步优化
使用ResizeObserver动态监听表格尺寸变化
const useTableScrollY = ({
extraHeight = 66,
ref,
}: {
extraHeight?: number;
ref: RefObject<HTMLDivElement>;
}) => {
const setTableBodyHeight = (tBody: HTMLElement, top: number) => {
const height = `calc(100vh - ${top + extraHeight}px)`;
const existingStyle = tBody.style.maxHeight;
if (existingStyle !== height) {
tBody.style.maxHeight = height;
tBody.style.minHeight = height;
tBody.style.overflowY = scrollElement ? "scroll" : "auto";
}
};
/**
* scrollElement 用于解决表格fixed时出现错位的问题
* 给表格y轴设置overflow-y: scroll时就不会出现错位
*/
const scrollElement: HTMLElement | null | undefined =
ref.current?.querySelector(
".ant-table-cell-fix-right.ant-table-cell-fix-right-first"
);
useEffect(() => {
const currentRef = ref.current;
const tBody = currentRef?.querySelector(
"div.ant-table-body"
) as HTMLElement;
if (!tBody) {
console.warn("ProTable 必须设置 scroll 属性");
return;
}
const resizeObserver = new ResizeObserver(() => {
if (tBody) {
const { top } = tBody.getBoundingClientRect();
setTableBodyHeight(tBody, top);
}
});
resizeObserver.observe(tBody);
return () => {
resizeObserver.disconnect();
};
}, [extraHeight, ref.current]);
};
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
# Antd Table column设置ellipsis不生效
设置onCell后即可
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
width: 200,
ellipsis: true,
onCell: (record) => ({
style: {
maxWidth: 200,
}
}),
2
3
4
5
6
7
8
9
10
11
# column的render打印出来是dom,而不是数据
render: (value, record) => {
console.log('value: ', value);
return value === 2 ? '成功' : '失败';
},
2
3
4

这里的value打印出来不会是2,这里应该使用renderText
renderText: (value, record) => {
console.log('value: ', value);
return value === 2 ? '成功' : '失败';
},
2
3
4
# Antd Table rowSelection
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
console.log('selectedRowKeys changed: ', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys);
};
<Table
loading={tableLoading}
columns={bavColumns}
dataSource={data}
rowSelection={{
type: 'radio',
selectedRowKeys,
onChange: (selectedRowKeys: React.Key[], selectedRows) => {
console.log('selectedRowKeys: ', selectedRowKeys, selectedRows);
setSelectedRowKeys(selectedRowKeys);
},
}}
></Table>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
默认情况下Table组件的 rowSelection 功能需要通过点击复选框来触发。如果希望点击表格的任意位置都能选中行,可以自定义行的点击事件
const handleRowClick = (record) => {
const isSelected = selectedRows.some(row => row.id === record.id);
if (isSelected) {
setSelectedRows(selectedRows.filter(row => row.id !== record.id));
} else {
setSelectedRows([...selectedRows, record]);
}
};
<Table
columns={columns}
dataSource={data}
rowSelection={{
selectedRowKeys: selectedRows.map(row => row.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows);
},
}}
onRow={(record) => ({
onClick: () => handleRowClick(record),
})}
/>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# CSS Modules修改Antd内部样式
如同Vue的:deep一样,CSS Module使用:global来实现
.listWrapper {
:global {
.ant-list-items {
max-height: 400px;
overflow-y: auto;
}
}
}
.listWrapper {
:global .ant-list-items {
max-height: 400px;
overflow-y: auto;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# TimePicker.RangePicker value回显
<TimePicker.RangePicker
value={[
moment(formState.executePlan.startDate, "HH:mm"),
moment(formState.executePlan.endDate, "HH:mm"),
]}
format={"HH:mm"}
onChange={(time, timeString) => {
setFormState({
executePlan: {
...formState.executePlan,
startDate: timeString[0],
endDate: timeString[1],
},
});
}}
/>;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
当清空RangePicker的值时会遇到显示validate Time的问题
const showRangePickerValue = () => {
if (formState.executePlan.startDate && formState.executePlan.endDate)
return [
moment(formState.executePlan.startDate, "HH:mm"),
moment(formState.executePlan.endDate, "HH:mm"),
];
return null;
};
<Item label="启动时间" required>
<TimePicker.RangePicker
value={showRangePickerValue()}
format={"HH:mm"}
onChange={(time, timeString) => {
setFormState({
executePlan: {
...formState.executePlan,
startDate: timeString[0],
endDate: timeString[1],
},
});
}}
/>
</Item>;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# TimePicker的国际化
修改DatePicker的locale
import moment from 'moment';
import 'moment/locale/zh-cn'; // 导入中文语言包
moment.locale('zh-cn'); // 设置 moment 使用中文语言包
2
3
4
首先导入了 moment.js 库,并导入了中文语言包。然后,使用 moment.locale() 方法将日期库设置为中文语言。
# Antd的Card组件
<Card title="123" style={{ height: 300 }}>
<div style={{ height: '100%' }}>456</div>
</Card>
2
3

最终设置的div是在ant-card-body内部的
正确的做法是给Card组件设置bodyStyle属性并设置height
# Message组件报错
[antd: message] Static function can not consume context like dynamic theme. Please use 'App' component instead.
https://www.jianshu.com/p/ea0d6159b8ff
# Antd Table column record[dataIndex] 获取值
Antd Table column dataIndex是个数组,然后在column的render中通过record[dataIndex]来获取值
发现获取不到
record[`${col.dataIndex}`]
# Antd的按需加载
在之前我们通过babel-plugin-import或vite-plugin-imp来实现Antd在Webpack和Vite下的按需加载
Webpack
module.exports = {
plugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es', // 或者是 'lib'
style: 'css', // 如果需要加载 less 文件,将 'css' 改为 'true'
},
],
],
};
2
3
4
5
6
7
8
9
10
11
12
Vite
export default defineConfig({
plugins: [
react(),
vitePluginImp({
libList: [
{
libName: 'antd',
style: (name) => `antd/es/${name}/style`,
},
],
}),
],
});
2
3
4
5
6
7
8
9
10
11
12
13
这样一来,在使用 Ant Design 组件时,就会自动按需引入样式文件
在Antd 5.x版本开始后,无需额外的 vite-plugin-imp,官方已经对 按需加载 (opens new window) 进行了优化,默认支持了基于 ESModules 的按需加载机制