打字稿否决泛型 [key: string]

时间:2021-06-02 16:42:11

标签: typescript

我有一个在整个应用程序中使用的 FormMeta 类型:

export type FormMeta = {
  validationSchema?: EntityValidationSchemaType;
  fields: { [fieldName: string]: FormField };
  inEditMode?: boolean;
  isDirty?: boolean;
  generalErrors?: { error: string }[];
};

fieldsFormMeta 属性非常模糊,并禁用了许多我希望以某种方式修复的 IntelliSense 功能。

无论我在何处创建此 FormMeta 类型的对象,我都可以正确填写 fields

export const formMetaFilter = (): FormMeta => ({
  fields: {
    filter: { ... },
    name: { ... },
    sortBy: { ... },
  },
});

如果我不将此对象输入为 FormMeta,TS 将能够正确建议所有(且仅)fields 非常棒且正是我想要的: Proper IntelliSense

然而,其他需要 FormMeta 类型的方法不会接受这个无类型对象,因为 'filter' !== [key: string]Cannot match keys to generic string

当我将对象键入为 FormMeta 类型时,这些方法将接受该对象,但 IntelliSense 会在 [key: string] 部分发生故障,仅在下一个 . 之后提供自动完成,其中转向导致容易出错的代码。

我想要达到的目标:

我目前使用以下代码:

export type FormMeta<T extends string> = {
  fields: { [fieldName in T]: FormField };
  // ...
};

export const formMetaFilter = (): FormMeta<'filter' | 'name' | 'sortBy'> => ({
  fields: {
    filter: { ... },
    name: { ... },
    sortBy: { ... },
  },
});

hasEntityErrors(formMeta: FormMeta<string>): boolean { ... }

实现了我想要的一切:正确的自动完成、检测不存在的 fieldNames 和正确的 FormMeta 识别,但编写和维护非常繁琐。有没有办法让 Typescript 查看这些更详细的键并在没有手动提示的情况下否决 [key: string] ?我的 FormMeta 类型可以轻松容纳多达 50 个 fields 并且我在整个代码库中使用了数百个...

我想在不使用类的情况下解决这个问题。允许使用接口,但仍首选类型。

1 个答案:

答案 0 :(得分:1)

我想我通过实现一个 createFormMeta 函数来实现您正在寻找的东西,该函数将字段与元数据的其余部分分开。

这避免了您必须像在 FormMeta<"filter" | "name" | "sortBy"> 中那样手动编写字段类型。

我不清楚这个解决方案是否适合您的特定用例。

type EntityValidationSchemaType = any; // not provided in the question
type FormField = any; // FormField was not provided in the question 

type FormMetaWithoutFields = {
    validationSchema?: EntityValidationSchemaType;
    inEditMode?: boolean;
    isDirty?: boolean;
    generalErrors?: { error: string }[]; 
}

type FormMeta<T extends Record<string, FormField>> = FormMetaWithoutFields & {
    fields: T;
};

const createFormMeta = <T extends Record<string, FormField>>(fields: T, meta: FormMetaWithoutFields): FormMeta<T> => {
    return { ...meta, fields };
};

// You can hover on this function and see that it automatically infers
// the return type as 
// FormMeta<{
//     fields: {
//         filter: string;
//         name: string;
//         sortBy: string;
//     };
// }>
const formMetaFilter = () => createFormMeta(
    {
        fields: {
            filter: "",
            name: "",
            sortBy: "",
        },
    },
    {} // empty metadata, but this can contain properties like `validationSchema` etc.
);

// To be implemented
declare const hasEntityErrors: <T extends Record<string, FormField>>(formMeta: FormMeta<T>) => boolean