typescript 是否可以使用索引签名来定义接口的形状?

58wvjzkj  于 2023-04-22  发布在  TypeScript
关注(0)|答案(2)|浏览(100)

我是TypeScript的新手,一直在寻找答案,但我很难找到我想要的东西。我看过Generic Constraints,但我认为它不太正确。
所以如果我有一个Form的接口,看起来像这样:

interface MyFirstForm {
    myField: {
        value: string;
        valid: boolean;
        myFirstArbitraryField: string
    }
}

然后我想像这样定义另一个Form

interface MyOtherForm {
    myField: {
        value: string;
        valid: boolean;
        someOtherArbitraryField: number
    }
}

很明显,valuevalid对于这两个是常见的,所以我可能应该以某种方式提取它们。
在TypeScript文档中,他们说当你知道某个东西的形状但不知道它的值会被调用时,可以使用“索引签名”。那么在这种情况下,我们可以使用一个带有Index signature的接口,并以任意形式扩展它吗?
类似这样的工作:

// The common values
interface FormField {
    value: string;
    valid: boolean;
}

// The common shape that we decide all our forms must conform to.
// Record<string, any> because we want to allow for extending the field objects
interface Form {
    [key: string]: FormField & Record<string, any>;
}

// We define our Record from the Form interface so we do not loose type safety
interface ArbitraryForm extends Form {
    myField: {
        value: string;
        valid: boolean;
        arbitraryField: number
    } // & FormField would also be valid
}

然而,通常当我扩展一个接口时,我不必指定我正在扩展的接口的值。在这种情况下,如果我从ArbitraryForm属性中省略valuevalid字段,我将得到一个类型错误。
我想要实现的是一种扩展接口的“形状”的方法,这样我就知道每当我在应用程序中创建表单时,我都应该扩展我的Form接口,并且我将拥有最不可行的表单。
像这样(尽管这会导致类型错误):

interface FormField {
    value: string;
    valid: boolean;
}

interface Form {
    [key: string]: FormField & Record<string, any>;
}

interface ArbitraryForm extends Form {
    myField: {
        arbitraryField: number
    }
}

例如,如果我有一个检查有效性的函数,我就可以这样做

function isFormValid(form: Form): boolean {
    return Object.values(form).every((field) => field.valid);
}

感谢任何答案或指针,我可以找到他们!

rekjcdws

rekjcdws1#

TypeScript中的对象类型已经是“开放的”或“可扩展的”,并且将允许额外的属性而不需要索引签名,并且由于将嵌套属性与索引签名相结合可能很棘手,让我们看看我们是否可以不这样做。
如果我们想要的只是将表单的每个属性与FormField合并,我们可以创建一个mapped type,通过交集来实现:

type AsForm<T> = { [K in keyof T]: T[K] & FormField }

interface ArbitraryForm extends AsForm<{
  myField: {
    arbitraryField: number
  }
}> { }

const aform: ArbitraryForm = {
  myField: {
    arbitraryField: 1,
    valid: true,
    value: "abc"
  }
}

同样,因为你可以有额外的属性,你的isFormValid()只需要指定属性的类型为FormField,它就可以工作了:

type Form = Record<string, FormField>;
function isFormValid(form: Form): boolean {
  return Object.values(form).every((field) => field.valid);
}

isFormValid(aform); // okay

Playground链接到代码

g9icjywg

g9icjywg2#

遗憾的是,使用这些接口,您无法扩展基本接口的属性,但是有一种替代解决方案,即使用type而不是interface

type FormField = {
  value: string;
  valid: boolean;
};

type MakeFormType<T extends {}> = T & FormField

type LoginForm = MakeFormType<{ name: string; surname: string }>;

更新

更新以覆盖每个键,而不是整个对象

type FormField = {
  value: string;
  valid: boolean;
};

type MakeFormType<T extends {}> = {
  [K in keyof T]: T[K] & FormField;
};

type LoginForm = MakeFormType<{
  field1: { name: string; surname: string };
  field2: { name: string; surname: string };
}>;

相关问题