首页 > 解决方案 > 打字稿可以在对象分配期间推断其他属性的类型

问题描述

我正在开发一个大型 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 是否有可能在分配期间动态推断该类型,还是我在寻找不存在的东西?

标签: typescripttype-inferenceinference

解决方案


您是正确的,您需要使用泛型来正确键入它。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

打字稿游乐场链接


推荐阅读