使用 TypeScript 显式键入对象 [英] Explicitly typing an object with TypeScript
问题描述
我正在将我的小库从 JavaScript 转换为 TypeScript,我在那里有一个函数
I'm working on converting my little library from JavaScript to TypeScript, and I have a function there
function create(declarations: Declarations) {
现在声明是一个对象,其键可以是两种类型:
Now the declaration is an object the keys of which can be of 2 types:
- 如果键是这些字符串之一:
onMemeber
/onCollection
,那么值应该是一个数字 - 对于任何其他字符串键,值应该是字符串
- If the keys are one of those strings:
onMemeber
/onCollection
, then the value should be a number - For any other string keys the value should be a string
这可以用 TypeScript 强制执行吗?我应该如何定义我的 Declarations
接口?
Is that possible to enforce with TypeScript? How should I define my Declarations
interface?
推荐答案
TypeScript 中没有具体的类型来表示您的 Declarations
形状.
There is no concrete type in TypeScript that represents your Declarations
shape.
我将总体思路称为默认属性"类型.(要求此的 GitHub 问题是 microsoft/TypeScript#17867)您想要特定属性是一种类型,然后任何其他类型默认"为其他不兼容的类型.它就像一个 索引签名,没有所有属性必须的约束可以分配给它.
I'd call the general idea a "default property" type. (GitHub issue asking for this is microsoft/TypeScript#17867) You want specific properties to be of one type, and then any others to "default" to some other incompatible type. It's like an index signature without the constraint that all properties must be assignable to it.
(需要明确的是,不能使用索引签名:
( Just to be clear, an index signature cannot be used:
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
,甚至onMember
和 onCollection
.要使索引签名真正有效,您需要将属性类型从 string
扩大到 string |number
,这可能对您不起作用.)
The index signature [k: string]: string
means every property must be assignable to string
, even onMember
and onCollection
. To make an index signature that actually works, you'd need to widen the property type from string
to string | number
, which probably doesn't work for you. )
有一些拉取请求使这成为可能,但看起来它们不会很快成为语言的一部分.
There were some pull requests that would have made this possible, but it doesn't look like they are going to be part of the language any time soon.
通常在 TypeScript 中,如果没有可用的具体类型,您可以使用 泛型类型 以某种方式约束.下面是我如何使 Declarations
通用:
Often in TypeScript if there's no concrete type that works you can use a generic type which is constrained in some way. Here's how I'd make Declarations
generic:
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
.这种自引用约束确保对于 declarations
的每个属性 K
,它的类型都是 K extends 'onMember' |'onCollection' ?数字:字符串
,一个条件类型,这是您所需形状的相当简单的翻译.
You can see that the declarations
parameter is of type T
, which is constrained to Declarations<T>
. This self-referential constraint ensures that for every property K
of declarations
, it will be of type K extends 'onMember' | 'onCollection' ? number : string
, a conditional type that is a fairly straightforward translation of your desired shape.
让我们看看它是否有效:
Let's see if it works:
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: ""}
代替.这太令人讨厌了,您可能想要使用辅助函数来允许为您推断此类类型,而不是手动注释:
Of course, using a generic type isn't without some annoyances; suddenly every value or function that you wanted to use Declarations
with will need to be generic now. So you can't do const foo: Declarations = {...}
. You'd need const foo: Declarations<{onCollection: number, foo: string}> = {onCollection: 1, foo: ""}
instead. That's obnoxious enough that you'd likely want to use a helper function like to allow such types to be inferred for you instead of manually annotated:
// 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;
}>*/
<小时>
好的,希望有帮助;祝你好运!
Okay, hope that helps; good luck!
这篇关于使用 TypeScript 显式键入对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!