一个困扰我许久的TypeScript定义问题

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

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

稀土掘金技术社区
掘金,一个帮助开发者成长的技术社区
 最新文章