首页 > 解决方案 > 使用 TypeScript 显式键入对象

问题描述

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

function create(declarations: Declarations) {

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

这可以用 TypeScript 强制执行吗?我应该如何定义我的Declarations界面?

标签: typescript

解决方案


TypeScript 中没有代表你的Declarations形状的具体类型。

我将一般概念称为“默认属性”类型。(要求此问题的 GitHub 问题是microsoft/TypeScript#17867)您希望特定属性为一种类型,然后将任何其他属性“默认”为其他不兼容的类型。它就像一个 索引签名,没有所有属性都必须分配给它的约束。

(为了清楚起见,不能使用索引签名:

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、 evenonMemberonCollection。要制作真正有效的索引签名,您需要将属性类型从stringto扩大string | number,这可能对您不起作用。)

有一些拉取请求可以使这成为可能,但看起来它们不会很快成为语言的一部分。

通常在 TypeScript 中,如果没有有效的具体类型,您可以使用以某种方式受到约束的泛型类型。以下是我如何制作通用的: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>。这种自引用约束确保对于 的每个属性Kdeclarations它将是 type K extends 'onMember' | 'onCollection' ? number : string,这是一种条件类型,是您所需形状的相当直接的转换。

让我们看看它是否有效:

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;
}>*/

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

链接到代码


推荐阅读