【问题标题】:Return type issue in generic function using conditional type使用条件类型的泛型函数中的返回类型问题
【发布时间】:2021-05-23 05:37:53
【问题描述】:

我正在尝试创建一个通用函数,该函数接受 FieldType 并返回一个对象,该对象包含来自公共基本类型的属性以及该特定类型 FormField 的默认值。出于某种原因,如果没有编译器抱怨,我就无法让它工作。我不确定问题出在哪里,但是

我怀疑这可能是FormField 类型被定义为条件类型的方式。我尝试以各种方式重新定义我的类型,但没有成功,所以我怀疑我要么错过了一些明显的东西,要么如果没有类型断言,这可能是不可能的。

这是我的类型:

type FieldType = 'text' | 'number' | 'custom'

interface FormFieldBase {
  type: FieldType
  label: string
  error: string
  disabled: boolean
}

interface CustomField extends FormFieldBase {
  value: string
}

interface TextField extends FormFieldBase {
  value: string
  placeholder: string
}

interface NumberField extends FormFieldBase {
  value: number
}

type FormField<T extends FieldType> = T extends 'text'
  ? TextField
  : T extends 'number'
  ? NumberField
  : CustomField

这是我的通用函数:

const createBaseField = <T extends FieldType>(
  type: T
): FormFieldBase => ({
  type,
  label: '',
  error: '',
  disabled: false
})

const defaultValues: {
  [K in FieldType]: Omit<FormField<K>, keyof FormFieldBase>
} = {
  text: {
    value: '',
    placeholder: ''
  },
  checkbox: {
    value: false
  },
  custom: {
    value: ''
  },
  number: {
    value: 0
  }
}

const fieldCreator = <T extends FieldType>(type: T): FormField<T> => ({
  ...createBaseField(type),
  ...defaultValues[type]
})


fieldCreator 产生以下错误:

类型'{类型:字段类型;标签:字符串;错误:字符串;禁用:布尔值; } & { number: Pick;文本:Pick;复选框:选择<...>;自定义:挑选<...>; }[T]' 不可分配给类型 'FormField'.ts(2322)

在我看来,FormFieldBase(来自createBaseField)和FormField&lt;T&gt; 中的字段(不包括FormFieldBase 中的字段(来自defaultValues)的交集应该是FormField&lt;T&gt;

我在这里做错了什么?

【问题讨论】:

    标签: typescript generics compiler-errors intersection conditional-types


    【解决方案1】:

    TLDR;

    FormField&lt;T&gt; 作为fieldCreator 函数的声明返回类型,对Defaults&lt;FieldType&gt; 一无所知。

    加长版

    请注意,您的 FormField&lt;T&gt; 类型仅保证解析的类型是 CustomFieldTextFieldNumberFieldextends 三个都是FormFieldBase 类型,所以也保证类型符合FormFieldBase 的形状,但没有别的

    现在,您的fieldCreator 函数签名声明它返回FormField&lt;T&gt;。让我们去掉这个显式注解,看看是不是这样:

    const a = fieldCreator("text"); //& Pick<TextField<"text">, "value" | "placeholder">
    

    为简洁起见,省略了FormFieldBase 类型。如您所见,返回类型在FormField&lt;T&gt; 中有关于其形状的附加信息不存在。正如您猜对的那样,由于spread properties 操作,此信息取自Omit&lt;FormField&lt;K&gt;, keyof FormFieldBase&gt;

    因此,当您明确地说fieldCreator 函数返回FormField&lt;T&gt; 时,编译器理所当然地抱怨事实并非如此。只需删除注释,让 TypeScript 完成它的工作并推断出正确的类型:

    const a = fieldCreator("text");
    a.value; //string
    a.placeholder; //string
    
    const b = fieldCreator("number");
    b.placeholder; //error
    b.value; //number
    

    顺便说一句:如果您使用泛型,请一直使用 - FormFieldBase 应该接受 FieldType 作为类型参数,以及扩展接口和默认常量的类型。请参阅playground link

    【讨论】:

    • 感谢您的明确答复!如果我们假设T 总是等于FieldType,我想Defaults 不需要是通用的。或者你这样做的任何原因?
    • @GustafLundström NP。 Re:generic - 嗯,它主要是为了保持一致性 - 因为您将 defaultValues 视为映射类型(它们是原始类型上的某种函数),所以将其设为通用对我来说是有意义的(甚至可能泛化以供重用: type GenericDefaults&lt;T, E, B&gt; = { [ K in T &amp; string ] : Omit&lt;E, keyof B&gt;; }).
    • 明白了。好建议。
    猜你喜欢
    • 2021-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-03-24
    • 1970-01-01
    相关资源
    最近更新 更多