稀土掘金技术社区 2024年11月16日
一个困扰我许久的TypeScript定义问题
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了一个在使用TypeScript定义函数参数时遇到的难题:如何确保函数参数中数组的每一项都符合特定的类型定义,并且能够在类型错误时给出准确的提示。作者尝试了多种TypeScript类型定义方式,但都存在问题,例如类型检查不严格或错误提示不准确。文章详细描述了遇到的问题和尝试过的解决方案,并分享了社区开发者提供的几种解决思路,希望能引发大家对TypeScript类型定义的思考和讨论。

🤔 **问题描述:**在定义包含组件类型和属性的数组参数时,TypeScript无法准确地检查数组每一项的属性类型,例如,当Input组件的props属性中错误地使用了string[]类型时,并未报错。

💡 **尝试方案一:**使用泛型和映射类型定义参数类型,但TypeScript未能严格检查props对象的结构,允许传入额外属性。

🚧 **尝试方案二:**使用联合类型定义参数类型,但错误信息提示不准确,例如,当Input组件的props属性中错误地使用了string[]类型时,提示缺少Select组件特有的options属性。

🐠 **社区解决方案一:**将type字段移至props中,使得TypeScript能够结合type和props进行类型判断,从而实现更精确的类型检查。

👨‍💻 **社区解决方案二:**利用索引类型和映射类型,构建更加灵活的类型定义,并确保类型检查的准确性。

原创 前端大骆 2024-11-16 09:01 重庆

点击关注公众号,“技术干货”及时达!

点击关注公众号,“技术干货” 及时达!

最近在使用TypeScript定义函数参数时,我遇到了一个难题,这让我感到困扰。我猜想这可能是因为我对TypeScript的掌握不够深入。虽然我问了无数次ChatGPT,但未能获得满意的解答。至今还没解决方案。今天我将这个问题分享出来,希望能听到大家的意见,看看是否有解决方案。

我在定义addComp函数时,设置了一个参数,该参数是一个数组。数组的每一项包含两个属性:type表示组件的类型,props包括组件的可配置属性。不同组件的props有共同的也有独特的属性,例如:

  [    {
      type'Input',
      props: {
        requiredtrue,
        defaultValue'默认值',
        placeholder'请输入',      },    },    {
      type'Select',
      props: {
        requiredtrue,
        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,问题显然出在propsdefaultValue类型上,但错误提示却显示缺少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没有吧typeprops结合起来一起判断类型,而是单独判断。

// 定义 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: []  }});

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

TypeScript 类型定义 函数参数 类型检查 错误提示
相关文章