可以在对象分配期间打字稿推断其他属性的类型

时间:2021-02-09 12:13:37

标签: typescript type-inference inference

我正在处理一个大型 Sanity.io CMS 项目,该项目涉及编写大量 JS 来定义如下内容类型:

const person = {
  type: 'document',
  name: 'person',
  fieldsets: [
    { name: 'personalDetails', title: 'Personal Details' },
  ],
  fields: [
    { name: 'firstName', type: 'string', fieldset: 'personalDetails' },
    { name: 'surname', type: 'string', fieldset: 'personalDetails' },
  ],
}

我想知道是否可以创建一个 Document 接口/类型来推断所提供的 fieldsets 的名称,然后使用它们来限制对象的 fieldset 属性的类型在 fields 数组中到字符串文字的联合。这是一件小事,但它可以让我在制作这些内容类型的同时利用自动完成功能,从而加快开发速度。

我最初的尝试是这样的:

interface Document {
  type: 'document';
  name: string;
  fieldsets: ReadonlyArray<{ name: string; title: string; }>;
  fields: Array<{
    name: string;
    type: string;
    fieldset: this['fieldset'][number]['name']; // Error: A 'this' type is available only in a non-static member of a class or interface
  }>
}

我了解错误并尝试了各种解决方法,但如果不诉诸使用泛型类型并提供字段集名称数组,则无法实现我的目标:

interface Document<T extends ReadonlyArray<string>> {
   /*  ...rest of definition  */
   fieldset: ReadonlyArray<{ name: T[number] }>
   fields: Array<{
     name: string;
     type: string;
     fieldset: T[number];
   }>
}

const person: Document<['personalDetails']> = {
  /* works, but not exactly what I'm after */
}

那么 TypeScript 是否有可能在赋值期间动态推断该类型,或者我是否在寻找不存在的东西?

1 个答案:

答案 0 :(得分:1)

您需要使用泛型来正确键入这是正确的。 Typescript 无法从常量推断泛型。它可以从函数调用中推断出泛型。所以你在这里要做的是定义一个标识函数作为创建类型安全文档的助手。

我希望通过使用类型 T 来描述整个 fieldsets 属性而不仅仅是名称来获得更好的推理。但是我仍然必须使用 as const 才能从对象属性中获取文字字符串值。

type FieldSet = {
    name: string;
    title: string;
}

type Field = {
    name: string;
    type: string;
    fieldset: string;
}

interface MyDocument<T extends ReadonlyArray<FieldSet>> {
    type: 'document';
    name: string;
    fieldsets: T;
    fields: Array<Field & {fieldset: T[number]['name']}>
}

const createDoc = <T extends ReadonlyArray<FieldSet>>(schema: MyDocument<T>): MyDocument<T> => schema;

const person = createDoc({
    type: 'document',
    name: 'person',
    fieldsets: [
        { name: 'personalDetails', title: 'Personal Details' }
    ] as const,
    fields: [
        { name: 'firstName', type: 'string', fieldset: 'personalDetails' },
        { name: 'surname', type: 'string', fieldset: 'otherSet' }, // error
    ],
});

// note: we need `as const` because the string is an object property

const identity = <T extends any>(value: T): T => value;

const a = identity('personalDetails'); // this infers a literal
const b = identity({ name: 'personalDetails', title: 'Personal Details' }); // this does not

Typescript Playground Link