Here is an example my object called MY_OBJECT
:
下面是我的一个名为My_Object的对象示例:
export type ObjectDetails = {
a: string,
b?: string
};
export type MyObjectType = { [key: string]: ObjectDetails };
export const MY_OBJECT: MyObjectType = {
BANANA: {
a: "a"
},
LEMON: {
a: "A"
}
}
export type MyObjectKey = keyof typeof MY_OBJECT;
Now a method should return an object that uses keys in MY_OBJECT
as keys (but doesn't have to use them all) and value can be a string
or a number
. So, I created this type:
现在,方法应该返回一个对象,该对象使用my_Object中的键作为键(但不必全部使用),值可以是字符串或数字。所以,我创建了这个类型:
type MyType = { [key: MyObjectKey]: string | number };
If I create this object and use a key NOT in MY_OBJECT
, how do I have to modify my code so that it would throw a compilation error?
如果我创建这个对象并使用不在my_Object中的键,我必须如何修改我的代码以使其抛出编译错误?
const newObject: MyType = {
BANANA: "banana",
APPLE: "apple" // <-- this should not compile
};
I think I understand correctly that it currently doesn't throw an error because I have set MY_OBJECT
key type as string
in MyObjectType
. Do I really have to first create a type for all possible keys in MY_OBJECT
and then use it in MyObjectType
? I would like to not do that.
我认为我正确地理解了它目前没有抛出错误,因为我已经在MyObjectType中将my_Object键类型设置为字符串。我真的必须首先为my_Object中所有可能的键创建一个类型,然后在MyObjectType中使用它吗?我不想那样做。
更多回答
There are several problems here. You annotated the type of MY_OBJECT
, obliterating any info from the initializer; you might want satisfies
instead. Your MyType
is invalid if you do that because index signatures cannot use a set of known keys; you should make it a mapped type instead. Excess properties cannot generally be prohibited, only discouraged. So, you can approach it as this playground link shows. Does that fully address the question? If so I'll write up an answer. If not, what am I missing?
这里有几个问题。您注释了MY_OBJECT的类型,删除了初始化器中的所有信息;您可能希望改为满足。如果这样做,您的MyType是无效的,因为索引签名不能使用一组已知键;您应该将其设置为映射类型。过多的房产通常不能被禁止,只能被劝阻。因此,您可以按照这个操场链接所示进行操作。这是否完全解决了这个问题?如果是的话,我会写一封回信。如果不是,我错过了什么?
In the playground, if you remove the LEMON
it gives compiler error Property 'LEMON' is missing in type '{ BANANA: string; }' but required in type 'MyType'.
. This was one criteria that object should not have to use all keys in MY_OBJECT
. || > You annotated the type of MY_OBJECT, obliterating any info from the initializer. I need values in MY_OBJECT
to be of the type.
在操场上,如果删除Lemon,它会给编译器带来错误,类型‘{banana:string;}’中缺少属性‘lemon’,但类型‘MyType’需要。这是对象不应该使用MY_OBJECT中所有键的一个条件。||>您注释了my_Object的类型,删除了初始值设定项中的所有信息。我需要MY_OBJECT中的值是类型。
Sorry I didn't catch the "don't have to use them all", just make the mapped type keys optional like this. I don't understand your "I need values in MY_OBJECT
to be of the type" comment. satisfies
should deal with that. If it doesn't work the way you want, please edit the question to show the unmet use case. If it does, I'll write up an answer. Please let me know.
抱歉,我没有注意到“不必全部使用”,只是像这样将映射类型键设置为可选的。我不理解你的“我需要MY_OBJECT中的值是类型”的评论。满足感应该解决这一点。如果它不能以您想要的方式工作,请编辑问题以显示未满足的用例。如果是的话,我会写一封回信的。请让我知道。
(see prev comment) just to make it clear, this is what happens if you make the value of the wrong type with satisfies
.
(请参阅上一条注释)只是为了说明一下,这就是如果您将错误类型的值设置为SUMMANSES时会发生的情况。
This works as I hoped, thanks! I will think a bit more if I could do something to simplify my code though. Something doesn't feel right.
这一切都如我所愿,谢谢!不过,如果我可以做一些事情来简化我的代码,我会考虑更多一点。感觉有些不对劲。
The main problem here is that when you annotate a variable like MY_OBJECT
with a (non-union) type like MyObjectType
, that's all the type checker will know about its type. It won't remember any more specific information about the variable's initializer; such knowledge is lost forever. So then typeof MY_OBJECT
is just MyObjectType
, and keyof typeof MY_OBJECT
is just string
.
这里的主要问题是,当您用MyObjectType这样的(非联合)类型注释My_Object这样的变量时,类型检查器所知道的关于它的类型就这么多了。它不会记住有关变量初始值设定项的任何更具体的信息;这样的知识将永远丢失。所以,my_Object的typeof就是MyObjectType,而my_Object的typeof typeof就是字符串。
Instead of annotating, you really just want to check that MY_OBJECT
's initializer is a valid MyObjectType
without widening it all the way to MyObjectType
. That looks like a job for the satisfies
operator. If, instead of const vbl: Type = expr
you write const vbl = expr satisfies Type
you will often get more desirable behavior:
您实际上只需要检查My_Object的初始值设定项是否为有效的MyObjectType,而不是将其完全扩展为MyObjectType,而不是进行注释。这看起来像是满意度操作员的工作。如果您编写的是const vbl:type=expr,而不是const vbl:type=expr,则通常会得到更理想的行为:
const MY_OBJECT = {
BANANA: {
a: "a"
},
LEMON: {
a: "A"
}
} satisfies MyObjectType // <-- do this instead
This still warns you if the initializer does not satisfy MyObjectType
:
如果初始值设定项不满足MyObjectType,则仍会发出警告:
const MY_OBJECT = {
BANANA: {
a: "a"
},
LEMON: {
a: 1 // error!
//~ <-- Type 'number' is not assignable to type 'string'.
}
} satisfies MyObjectType;
Now the type of MY_OBJECT
is more specific than MyObjectType
, which you can observe via IntelliSense:
现在My_Object的类型比MyObjectType更具体,您可以通过IntelliSense观察:
/* const MY_OBJECT: {
BANANA: { a: string; };
LEMON: { a: string; };
} */
and you can get its keys as desired:
你可以根据需要获得它的密钥:
type MyObjectKey = keyof typeof MY_OBJECT;
// type MyObjectKey = "BANANA" | "LEMON"
Now you can define MyType
accordingly. You can't use an index signature for this purpose; index signatures only support "wide" types like string
. You have a set of known keys. You should use a mapped type instead. Since you want the properties to be optional, you can use the ?
mapping modifier:
现在您可以相应地定义MyType了。您不能将索引签名用于此目的;索引签名仅支持“宽”类型,如字符串。您有一组已知的密钥。您应该改用映射类型。由于您希望这些属性是可选的,因此可以使用?贴图修改器:
type MyType = { [K in MyObjectKey]?: string | number };
/* type MyType = {
BANANA?: string | number | undefined;
LEMON?: string | number | undefined;
} */
And now you'll get the behavior you were looking for:
现在你会得到你想要的行为:
const newObject: MyType = {
BANANA: "banana",
APPLE: "apple" // <-- error
};
A final caveat, though. Excess property checks like the error on APPLE
above only happen when you use an object literal directly in a place expecting a type without the property. They are mostly a linter warning that you might be making a mistake by have made a mistake by using a key the compiler will immediately forget about. They are not intended to be a type error, and you can always evade the detection by doing the assignment in multiple steps, like this:
不过,最后要提的是。额外的属性检查,如上面Apple上的错误,仅当您在需要没有属性的类型的地方直接使用对象文本时才会发生。它们主要是Linter警告,您可能会因为使用了编译器会立即忘记的键而犯了错误。它们并不是一个类型错误,您可以通过分多个步骤进行赋值来避免检测,如下所示:
const obj = { BANANA: "banana", APPLE: "apple" };
const newObject2: MyType = obj; // this will never be an error.
That's fine according to the compiler because obj
knows about APPLE
. This is sort of out of scope here, since everything works as you wanted. But you should be aware of it anyway. See Typescript: prevent assignment of object with more properties than is specified in target interface or Why are excess properties allowed in Typescript when all properties in an interface are optional? or typescript: validate excess keys on value, returned from function or Unexpected behaviour of Typescript Record<number, Type> type, or any other question mentioning "excess properties" and "TypeScript", for more.
根据编译器的说法,这很好,因为obj知道苹果。这有点超出了本文的范围,因为一切都如您所愿地工作。但无论如何,你都应该意识到这一点。请参见TypeScrip:防止分配具有比目标接口中指定的属性更多的属性的对象,或者当接口中的所有属性都是可选的时,为什么在TypeScrip中允许多余的属性?或类型脚本:验证从函数返回的值上的多余键,或类型脚本记录
type的意外行为,或任何其他提到“额外属性”和“类型脚本”的问题,以获取更多信息。
Playground link to code
游乐场链接到代码
更多回答
Thank you! But I did some changes: playground. There is one drawback that I brought out, but it is okay for me because I will never use all original keys at once.
谢谢!但我做了一些改变:操场。我提出了一个缺点,但这对我来说是可以的,因为我永远不会同时使用所有原始密钥。
我是一名优秀的程序员,十分优秀!