使用TypeSCript明确键入对象

时间:2019-10-29 17:08:41

标签: typescript

我正在将我的小库从JavaScript转换为TypeScript,并且在那里有一个函数

function create(declarations: Declarations) {

现在,声明是一个对象,其键可以为两种类型:

  • 如果键是以下字符串之一:onMemeber / onCollection,则值应为数字
  • 对于任何其他字符串键,该值应为字符串

可以使用TypeScript强制执行吗?我应该如何定义我的Declarations接口?

2 个答案:

答案 0 :(得分:1)

这似乎是不可能的,in the TypeScript Deep Dive book上有专门的章节。尽管可以声明类型,但实际上无法在TS中使用它创建对象:

// No error on this type declaration:
type Declarations = {
    [key: string]: string;
} & {
    onMember: number;
    onCollection: number;
}

// Error does appear here indicating type `number` is not assignable to type `string`.
const declarations: Declarations = {
    onMember: 0,
    onCollection: 0,
    other: 'Is a string'
}

TypeScript Playground link

答案 1 :(得分:1)

TypeScript中没有具体的类型可以表示您的Declarations形状。

我将一般概念称为“默认属性”类型。 (要求这是microsoft/TypeScript#17867的GitHub问题)您希望特定属性属于一种类型,然后让其他属性“默认”为其他一些不兼容的类型。就像index signature,没有所有属性都必须可分配给它的约束。

(请注意,不能使用索引签名:

type BadDeclarations = {
    onMember: number, // error! number not assignable to string
    onCollection: number, // error! number not assignable to string
    [k: string]: string
};

索引签名[k: string]: string表示每个属性必须可分配给string,甚至onMemberonCollection。要使索引签名真正起作用,您需要将属性类型从string扩展到string | number,这可能对您不起作用。 )

有一些pull requests that would have made this possible,但看起来它们很快不会成为语言的一部分。

通常在TypeScript中,如果没有适用的具体类型,则可以以某种方式使用generic type,即constrained。这是使Declarations通用的方法:

type Declarations<T> = {
    [K in keyof T]: K extends 'onMember' | 'onCollection' ? number : string
};

这是create() L

的签名
function create<T extends Declarations<T>>(declarations: T) {
}

您可以看到declarations参数的类型为T,它被约束为Declarations<T>。此自引用约束确保对于K的每个属性declarations,它的类型为K extends 'onMember' | 'onCollection' ? number : string,是conditional type的类型,可以很容易地转换出所需形状。

让我们看看它是否有效:

create({
    onCollection: 1,
    onMember: 2,
    randomOtherThing: "hey"
}); // okay

create({
    onCollection: "oops", // error, string is not assignable to number
    onMember: 2,
    otherKey: "hey",
    somethingBad: 123, // error! number is not assignable to string
})

在我看来,这很合理。


当然,使用泛型类型并非没有烦恼。突然之间,您想要与Declarations一起使用的每个值或函数现在都必须是通用的。因此,您无法执行const foo: Declarations = {...}。您需要使用const foo: Declarations<{onCollection: number, foo: string}> = {onCollection: 1, foo: ""}来代替。令人讨厌的是,您可能想要使用一个辅助函数,例如允许为您推断此类类型,而不是手动添加注释:

// helper function
const asDeclarations = <T extends Declarations<T>>(d: T): Declarations<T> => d;

const foo = asDeclarations({ onCollection: 1, foo: "a" });
/* const foo: Declarations<{
    onCollection: number;
    foo: string;
}>*/

好的,希望能有所帮助;祝你好运!

Link to code