React 相关问题
1. TypeScript泛型
const mergedColumns = columns.map<TableProps<DataType>>((col) => {
const mergedColumns: TableProps<DataType>['columns'] = columns.map((col) => {
Argument of type '(col: { title: string; dataIndex: string; width: string; editable: boolean; } | { title: string; dataIndex: string; width?: undefined; editable?: undefined; }) => { title: string; dataIndex: string; width: string; editable: boolean; } | { ...; } | { ...; }' is not assignable to parameter of type '(value: { title: string; dataIndex: string; width: string; editable: boolean; } | { title: string; dataIndex: string; width?: undefined; editable?: undefined; }, index: number, array: ({ title: string; dataIndex: string; width: string; editable: boolean; } | { ...; })[]) => TableProps<...>'.
Type '{ title: string; dataIndex: string; width: string; editable: boolean; } | { title: string; dataIndex: string; width?: undefined; editable?: undefined; } | { onCell: (record: any) => { record: any; dataIndex: string; title: string; }; title: string; dataIndex: string; width: string; editable: boolean; }' is not assignable to type 'TableProps<DataType>'.
Type '{ title: string; dataIndex: string; width: string; editable: boolean; }' is not assignable to type 'TableProps<DataType>'.
Types of property 'title' are incompatible.
Type 'string' is not assignable to type 'PanelRender<DataType>'.ts(2345)
Type '{ title: string; dataIndex: string; width: string; editable: boolean; } | { title: string; dataIndex: string; width?: undefined; editable?: undefined; } | { onCell: (record: any) => { record: any; dataIndex: string; title: string; }; title: string; dataIndex: string; width: string; editable: boolean; }' is not assignable to type 'TableProps<DataType>'.
Type '{ title: string; dataIndex: string; width: string; editable: boolean; }' is not assignable to type 'TableProps<DataType>'.
Types of property 'title' are incompatible.
Type 'string' is not assignable to type 'PanelRender<DataType>'.ts(2345)
(parameter) col: {
title: string;
dataIndex: string;
width: string;
editable: boolean;
} | {
title: string;
dataIndex: string;
width?: undefined;
editable?: undefined;
}
title: string;
dataIndex: string;
width: string;
editable: boolean;
} | {
title: string;
dataIndex: string;
width?: undefined;
editable?: undefined;
}
解答:
为什么引用时候,一直 Expected 1 arguments, but got 0.
// 泛型 <TableProps<DataType>> 修饰的是"回调函数的返回值"
const result = columns.map<TableProps<DataType>>((col) => {
// 必须返回 TableProps<DataType>
return {
dataSource: [], // ✅ TableProps 有 dataSource
columns: [], // ✅ TableProps 有 columns
pagination: false, // ✅ TableProps 有 pagination
// ... 所有属性都要有
};
});
2. 写了一个hooks
import { DownOutlined } from '@ant-design/icons';
import { Dropdown, Space, type MenuProps } from 'antd';
import React from 'react';
const useOperation: React.FC = () => {
const items: MenuProps['items'] = [
{
label: (
<a href="https://www.antgroup.com" target="_blank" rel="noopener noreferrer">
1st menu item
</a>
),
key: '0',
},
{
label: (
<a href="https://www.aliyun.com" target="_blank" rel="noopener noreferrer">
2nd menu item
</a>
),
key: '1',
},
{
type: 'divider',
},
{
label: '3rd menu item',
key: '3',
},
];
return (
<>
<Dropdown menu={{ items }} trigger={['click']}>
<a onClick={(e) => e.preventDefault()}>
<Space>
Click me
<DownOutlined />
</Space>
</a>
</Dropdown>
</>
);
};
export default useOperation;
为什么引用时候,一直 Expected 1 arguments, but got 0.
解答:
// ❌ 错误:useOperation 被定义为一个 React 组件
const useOperation: React.FC = () => { ... }
// ✅ 正确:Hook 应该返回数据或方法,而不是 JSX
const useOperation = () => { ... }
3、这个hooks方法import { PermissionName } from '@/common/consts';
import { formatMessage } from '@/common/messages';
import { MSG012, MSG031 } from '@/common/messages/messages';
import { checkRowEditable } from '@/common/utils/common';
import type { MasterEmployee } from '@/models';
import {
ArrowDownOutlined,
ArrowUpOutlined,
CloseCircleOutlined,
DeleteOutlined,
EditOutlined,
ExclamationCircleFilled,
MenuOutlined,
SaveOutlined,
} from '@ant-design/icons';
import { App, Button, Dropdown, type ButtonProps, type MenuProps } from 'antd';
import { useEffect, useMemo, useRef } from 'react';
interface DeleteConfirmOptions<T> {
/** Title prefix, e.g. "行" -> "行削除確認" */
title: string;
/** Content subject or function to get content from record, e.g. "行" -> "行を削除してもよろしいでしょうか?" */
content: string | ((record: T) => string);
}
interface ColumnProps<T> {
employee?: MasterEmployee;
fromScreen?: string;
size?: ButtonProps['size'];
editingKey: string | number | null;
rowKey: keyof T;
onSave: (record: T) => void;
onCancel: (record: T) => void;
onEdit: (record: T) => void;
onDelete: (record: T) => void;
onAddBefore?: (record: T) => void;
onAddAfter?: (record: T) => void;
/** Custom delete confirmation dialog options */
deleteConfirm?: DeleteConfirmOptions<T>;
/** When false, disables the operation dropdown menu */
canSave?: boolean; // buttonPermissions
deleteMenuLabel?: string;
}
/**
* Hook that returns an operation column renderer for table.
* Uses App.useApp() to get modal and message instances that can consume ConfigProvider context.
*/
const useOperationColumn = <T extends object>(options: ColumnProps<T>) => {
const { modal, message } = App.useApp();
const { size = 'small' } = options;
const { deleteMenuLabel = '行を削除' } = options;
// Track active delete confirmation modal to destroy on unmount
const activeModalRef = useRef<ReturnType<typeof modal.confirm> | null>(null);
// Destroy confirmation modal when component unmounts (e.g. browser back navigation)
useEffect(() => {
return () => {
activeModalRef.current?.destroy();
};
}, []);
// SC050 取引管理室
const canShowDelete = useMemo(() => {
return options?.fromScreen === 'SC050' ? options?.employee?.permissionName === PermissionName.ALL : true;
}, [options]);
const hasTeamPermission = (record: T): boolean => {
if (!options.employee) {
return true;
}
return checkRowEditable(record, options.employee);
};
return (_: unknown, record: T) => {
const isEditing = record[options.rowKey] === options.editingKey;
const baseDisabledRule = !options.canSave || (!!options.editingKey && !isEditing);
const getDisabledResult = (key: string): boolean => {
if (key === 'delete') {
return record['permissionName']
? record['permissionName'] === PermissionName.ALL // permission 取引管理室
: baseDisabledRule || !hasTeamPermission(record);
}
if (key === 'edit') {
return baseDisabledRule || !hasTeamPermission(record);
}
return baseDisabledRule;
};
// Show delete confirmation dialog
const showDeleteConfirm = () => {
const content = `${formatMessage(MSG031)}`;
activeModalRef.current = modal.confirm({
title: MSG031.title,
icon: <ExclamationCircleFilled />,
content,
okText: 'OK',
cancelText: 'キャンセル',
onOk() {
activeModalRef.current = null;
options.onDelete(record);
},
onCancel() {
activeModalRef.current = null;
},
});
};
const editingItems: MenuProps['items'] = [
{
key: 'save',
label: '行を保存',
icon: <SaveOutlined />,
onClick: () => options.onSave(record),
},
{
key: 'cancel',
label: 'キャンセル',
icon: <CloseCircleOutlined />,
onClick: () => options.onCancel(record),
},
];
const normalItems: MenuProps['items'] = [
{
key: 'edit',
label: '行を編集',
icon: <EditOutlined />,
disabled: getDisabledResult('edit'),
onClick: () => options.onEdit(record),
},
...(options.onAddBefore
? [
{
key: 'addBefore',
label: '上に行を追加',
icon: <ArrowUpOutlined />,
disabled: getDisabledResult('addBefore'),
onClick: () => {
options.onAddBefore?.(record);
message.success(MSG012.template);
},
},
]
: []),
...(options.onAddAfter
? [
{
key: 'addAfter',
label: '下に行を追加',
icon: <ArrowDownOutlined />,
disabled: getDisabledResult('addAfter'),
onClick: () => {
options.onAddAfter?.(record);
message.success(MSG012.template);
},
},
]
: []),
...(canShowDelete
? [
{
key: 'delete',
danger: true,
label: deleteMenuLabel,
icon: <DeleteOutlined />,
disabled: getDisabledResult('delete'),
onClick: showDeleteConfirm,
},
]
: []),
];
const menuItems = isEditing ? editingItems : normalItems;
return (
<Dropdown menu={{ items: menuItems }} trigger={['click']} disabled={baseDisabledRule}>
<Button type="link" size={size} icon={<MenuOutlined />} />
</Dropdown>
);
};
};
export default useOperationColumn;
解答:
1. antd, Table操作栏必须返回一个函数,所以当前hooks,return 了一个函数。 而且record属性,是默认自带的。 像下面这种。
{
title: 'operation',
dataIndex: 'operation',
render: (_: any, record: DataType) => {
return (_: unknown, record: T) => {
3. 解释泛型
const useOperationColumn = <T extends object>(options: ColumnProps<T>)
const useOperationColumn = <T>(options: ColumnProps<T>) => {
return (_, record: T) => {
// T 可以是任何类型
};
};
// 用于 User
useOperationColumn<User>({ ... });
// 用于 Product
useOperationColumn<Product>({ ... });
// 用于任何类型
useOperationColumn<Order>({ ... });
// 定义三个不同的类型
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
title: string;
price: number;
}
interface Order {
id: number;
userId: number;
total: number;
status: 'pending' | 'completed';
}
type ColumnProps<T> = { fromScreen?: string; employee?: any; deleteMenuLabel?: string; size?: 'small' | 'middle' | 'large'; onEdit?: (record: T) => void; // ← 用到了 T onDelete?: (record: T) => void; // ← 用到了 T
里面用到了T,所以ColumnProps<T> 要加<T> , T是占位符,这个地方加上T之后,所以前面必须要写<T extends object> 这个来先声明T
问题4.
rowKey: keyof T;
<T>(options: ColumnProps<T>) 这样直接写报错 JSX element 'T' has no corresponding closing tag.ts(17008) Cannot find name 'T'.
问题5:
const [editingKey, setEditingKey] = useState<number | null>(null);
const isRowEditing = (record: DataType) => record.id === editingKey;
const handleEdit = (record: DataType) => {
console.log('编辑方法');
form.setFieldsValue({ ...record });
setEditingKey(record.id!);
};
const handleCancel = () => {
setEditingKey(null);
};

浙公网安备 33010602011771号