点击关注公众号,“技术干货” 及时达!
最近在使用TypeScript定义函数参数时,我遇到了一个难题,这让我感到困扰。我猜想这可能是因为我对TypeScript的掌握不够深入。虽然我问了无数次ChatGPT,但未能获得满意的解答。至今还没解决方案。今天我将这个问题分享出来,希望能听到大家的意见,看看是否有解决方案。
我在定义addComp
函数时,设置了一个参数,该参数是一个数组。数组的每一项包含两个属性:type
表示组件的类型,props
包括组件的可配置属性。不同组件的props
有共同的也有独特的属性,例如:
[
{
type: 'Input',
props: {
required: true,
defaultValue: '默认值',
placeholder: '请输入',
},
},
{
type: 'Select',
props: {
required: true,
defaultValue: ['1'],
placeholder: '请输入',
options: [
{
label: '选项一',
value: '1'
}
]
},
},
]
我对以上数据做了如下的TypeScript。
// 定义 props 属性的基础接口
interface CompProps {
required?: boolean;
placeholder?: string;
}
// 定义特定控件类型的 props 接口
interface InputProps extends CompProps {
defaultValue?: string;
}
interface SelectProps extends CompProps {
defaultValue?: string[] | number[];
options: Array<{ label: string, value: string }>;
}
type CompPropsMap = {
Input: InputProps;
Select: SelectProps;
};
// 定义组件类型的联合类型
type CompType = 'Input' | 'Select';
// 定义 AddCompParam 中每一项的接口
interface AddCompParam<T extends CompType> {
type: T;
props: CompPropsMap[T];
}
然而,在使用过程中发现了问题。例如,以下示例应该报错,但实际上并没有:
// 示例对象
const addCompParam: AddCompParam<CompType>[] =
[
{
type: 'Input',
props: {
required: true,
defaultValue: ['默认值'],// 这里应为string,错误地使用了string[]
placeholder: '请输入',
options: [],// 这是一个无关属性,不应存在于InputProps中
},
},
];
按理论来推动,此时应该会报错,但是实际不会报错。我尝试开启严格模式,重启TypeScript服务,还是不会报错。问 ChatGPT,得到的答复是
❝在您的例子中,
❞AddCompParam
的定义是基于泛型的,因此 TypeScript 会根据传入的类型进行推断。由于CompPropsMap
中的InputProps
和SelectProps
是通过继承和映射来定义的,TypeScript 可能没有对props
的结构进行严格的检查。具体来说,props
是一个对象,TypeScript 在类型检查时可能会允许额外的属性存在于对象中,只要对象中包含了所需的属性。这就是为什么在Input
的props
中添加了options
属性,TypeScript 仍然不会报错。
我对上面的答复不是很满意。为了让TypeScript 能够报错,我换了一种定义方式
// 定义 props 属性的基础接口
interface CompProps {
required?: boolean;
placeholder?: string;
}
// 定义特定控件类型的 props 接口
interface InputProps extends CompProps {
defaultValue?: string;
}
interface SelectProps extends CompProps {
defaultValue?: string[] | number[];
options: Array<{ label: string, value: string }>;
}
// 定义 AddCompParam 中每一项的接口
type AddCompParam =
{ type: 'Input'; props: InputProps } |
{ type: 'Select'; props: SelectProps };
然而,在实际使用过程中,我发现了另一个严重的问题:错误信息的提示并不准确。
// 示例对象
const addCompParam: AddCompParam[] =
[
{
type: 'Input',
props: {
required: true,
defaultValue: ['1'],
placeholder: '请输入',
},
},
];
报错信息如下所示:
❝不能将类型
❞{ required: true; defaultValue: string[]; placeholder: string; }
分配给类型InputProps | SelectProps
。
类型{ required: true; defaultValue: string[]; placeholder: string; }
中缺少属性options
,但类型SelectProps
中需要该属性。
这个错误提示令我困惑。虽然我明确将type
设置为Input
,问题显然出在props
的defaultValue
类型上,但错误提示却显示缺少options
属性。尽管我尝试补全了这个属性,错误提示改变了,但仍未指向defaultValue
的类型问题。
// 示例对象
const addCompParam: AddCompParam[] =
[
{
type: 'Input',
props: {
required: true,
defaultValue: ['1'],
placeholder: '请输入',
options:[]
},
},
];
报错信息如下所示:
❝不能将类型
❞{ type: "Input"; props: { required: true; placeholder: string; defaultValue: string[]; options: { label: string; value: string; }[]; }; }
分配给类型AddCompParam
。
属性props
的类型不兼容。
对象字面量只能指定已知属性,并且options
不在类型InputProps
中。
我再次询问 ChatGPT 得到的答复是
❝您遇到的错误提示是由于 TypeScript 处理联合类型时的类型推断机制。在您的代码中,
❞AddCompParam
是一个联合类型,它可以是Input
或Select
类型。当 TypeScript 检查addCompParam
数组中的对象时,它会尝试确保props
属性符合InputProps
或SelectProps
的定义。
❝在您的示例中,您定义了一个
❞Input
类型的对象,但是在props
中,您传递了一个defaultValue
的值为['1']
,这是一个字符串数组(string[]
),而InputProps
中的defaultValue
应该是一个字符串(string
)。因此,TypeScript 在尝试推断props
的类型时,发现defaultValue
的类型不匹配。
❝由于
❞props
的类型是InputProps | SelectProps
,TypeScript 会将props
视为可能是SelectProps
类型,因此它会检查SelectProps
的所有必需属性,包括options
。这就是为什么您会看到提示缺少options
属性。
目前我还不确定如何解决这个问题。如果按照下面的方式定义:
type AddCompParam = [
{ type: 'Input'; props: InputProps },
{ type: 'Select'; props: SelectProps }
];
这种定义方式似乎限制了addCompParam
参数中每个项的固定位置,例如type: 'Select'
必须位于addCompParam
数组的第二个位置。这并不是我想要的效果。
我尝试了多种方式向ChatGPT描述这个问题,但是得到的回答都未能提供正确的解决方案。可能是我的问题描述不够清晰,导致ChatGPT无法完全理解问题的核心。
如果各位掘友有遇到过类似问题并找到了解决方案,恳请在评论区分享一下,非常感谢!
掘友 鱼骨头jinx 的解决方案,感谢提供。
❝如果把
❞type
挪到props
中,报错信息是对的,感觉是层级的问题,ts没有吧type
和props
结合起来一起判断类型,而是单独判断。
// 定义 props 属性的基础接口
interface CompProps<T extends CompType> {
type: T; // 将 type 字段作为泛型参数
required?: boolean;
placeholder?: string;
}
// 定义特定控件类型的 props 接口
interface InputProps extends CompProps<'Input'> {
defaultValue?: string;
}
interface SelectProps extends CompProps<'Select'> {
defaultValue?: string[] | number[];
options: Array<{ label: string, value: string }>;
}
type CompPropsMap = {
Input: InputProps;
Select: SelectProps;
};
// 定义组件类型的联合类型
export type CompType = 'Input' | 'Select';
// 定义 AddCompParam 中每一项的接口
interface AddCompParam<T extends CompType> {
type: T;
props: CompPropsMap[T];
}
export default AddCompParam;
// 示例对象
const addCompParam: AddCompParam[] =
[
{
type: 'Input',
props: {
type: 'Input',
required: true,
placeholder: '请输入',
defaultValue: ['1'],
},
},
{
type: 'Select',
props: {
type: 'Select',
required: true,
placeholder: '请输入',
defaultValue: ['1'],
options: [],
},
},
];
掘友 用户4512308223327 的解决方案,感谢提供。
// 定义 props 属性的基础接口
interface CompProps<T extends CompType> {
type: T; // 将 type 字段作为泛型参数
required?: boolean;
placeholder?: string;
}
// 定义特定控件类型的 props 接口
interface InputProps extends CompProps<'Input'> {
defaultValue?: string;
}
interface SelectProps extends CompProps<'Select'> {
defaultValue?: string[] | number[];
options: Array<{ label: string, value: string }>;
}
type CompPropsMap = {
Input: InputProps;
Select: SelectProps;
};
// 定义组件类型的联合类型
export type CompType = keyof CompPropsMap;
type AddCompParam = {
[key in CompType] :{
type :key;
props:CompPropsMap[key]
}
}[CompType]
// 示例对象
const addCompParam: AddCompParam[] =
[
{
type: 'Input',
props: {
type: 'Input',
required: true,
placeholder: '请输入',
defaultValue: ['1'],
},
},
{
type: 'Select',
props: {
type: 'Select',
required: true,
placeholder: '请输入',
defaultValue: ['1'],
options: [],
},
},
];
掘友 何缘小夏 的解决方案,感谢提供。
// 定义 props 属性的基础接口
interface CompProps {
required?: boolean;
placeholder?: string;
}
// 定义特定控件类型的 props 接口
interface InputProps extends CompProps {
defaultValue?: string;
}
interface SelectProps extends CompProps {
defaultValue?: string[] | number[];
options: Array<{ label: string; value: string }>;
}
type CompPropsMap = {
Input: InputProps;
Select: SelectProps;
};
// 定义组件类型的联合类型
type CompType = 'Input' | 'Select';
// 定义 AddCompParam 中每一项的接口
interface AddCompParam<T extends CompType> {
type: T;
props: CompPropsMap[T];
}
function addComp<T extends CompType>(param: AddCompParam<T>) {
return param;
}
addComp({
type: 'Select',
props: {
required: true,
defaultValue: [8], // 这里应为string,错误地使用了string[]
placeholder: '请输入',
// 这是一个无关属性,不应存在于InputProps中
options: []
}
});
点击关注公众号,“技术干货” 及时达!