英文:
Why keyof operator retreives non enumerable inherited? properties from object literal type?
问题
请考虑以下的TypeScript代码:
'use strict';
type Value = 1 | 2 ;
type Owner = 'ownerA' | 'ownerB';
type ItemType = 'itemTypeA' | 'itemTypeB';
type Item = {
type: ItemType;
owner: Owner;
value: Value;
};
type AnotherType = {
[k in keyof Item]: null | {
[m in keyof Item[k]]: number;
};
};
const v: AnotherType = {
type: { itemTypeA: 0, itemTypeB: 1,},
owner: { ownerA: 0, ownerB: 1,},
value: null,
};
console.log(v);
在这种情况下,tsc
抛出了一个错误:
error TS2322: Type '{ itemTypeA: number; itemTypeB: number; }' is not
assignable to type '{ [x: number]: number; toString: number; charAt:
number; charCodeAt: number; concat: number; indexOf: number;
lastIndexOf: number; localeCompare: …
显然,[m in keyof Item[k]]: number;
迭代出现了某种原因,包括了非枚举的,甚至不存在的属性(不确定对象类型是否可以继承自 Object.prototype 的类型),比如 charCodeAt、charAt 等等。
根据我所了解的情况,这与 关于 'keyof' 类型操作符的文档 矛盾,文档描述如下:
keyof 操作符接受一个对象类型并生成其键的字符串或数字字面量联合。以下类型 P 与类型 P = "x" | "y" 相同:`type Point = { x: number; y: number };
type P = keyof Point;`
即,生成的联合中没有 'charAt' 等值,只有 'x' 和 'y'。
也许与 `in` 操作符有关,但再次强调,在 JavaScript 中,它只遍历对象的可枚举属性(不确定在 TypeScript 中是否作为类型操作符使用时也是如此)。
有人能否解释一下发生了什么?
注: 以上是您提供的代码的翻译部分,不包括问题的回答。
英文:
Please, consider this TypeScript code:
'use strict';
type Value = 1 | 2 ;
type Owner = 'ownerA' | 'ownerB';
type ItemType = 'itemTypeA' | 'itemTypeB';
type Item = {
type: ItemType;
owner: Owner;
value: Value;
};
type AnotherType = {
[k in keyof Item]: null | {
[m in keyof Item[k]]: number;
};
};
const v: AnotherType = {
type: { itemTypeA: 0, itemTypeB: 1,},
owner: { ownerA: 0, ownerB: 1,},
value: null,
};
console.log(v);
The tsc
throws an error in this case:
> error TS2322: Type '{ itemTypeA: number; itemTypeB: number; }' is not
> assignable to type '{ [x: number]: number; toString: number; charAt:
> number; charCodeAt: number; concat: number; indexOf: number;
> lastIndexOf: number; localeCompare: …
Apparently the [m in keyof Item[k]]: number;
iteration for some reason includes the non-enumerable, even non-present properties (not sure if an object type can inherit from Object.prototype's type) like: charCodeAt, charAt etc.
Up to my knowledge this contradicts the documentation on the 'keyof' type operator, which is described as:
> The keyof operator takes an object type and produces a string or
> numeric literal union of its keys. The following type P is the same
> type as type P = "x" | "y": type Point = { x: number; y: number };
> type P = keyof Point;
i.e. there are no 'charAt' et al. values in the produced union, only 'x' and 'y'.
May be it has something to do with the in
operator, but again, in JS it traverses only thru enumerable properties of an object (not sure if that's true in TypeScript when it's used as a type operator).
Could someone please explain, what's happening here?
答案1
得分: 2
你应该将你的类型更改为类似这样的:
type AnotherType = {
[K in keyof Item]: null | {
[M in Item[K]]: number; // <-- no keyof
};
};
在这里,你不应该在嵌套的映射类型内使用 keyof
操作符。
M in keyof Item[K]
的问题在于 Item[K]
是一个索引访问类型,因此它是 Item
接口的 ItemType
、Owner
或 Value
中的一个,这些已经是你想要的键样的字面类型的联合。而且,由于 Item[K]
已经是键样的,写 keyof Item[K]
实际上是在评估键的键。假设你不关心字符串键如 "ownerA"
(例如,"toUpperCase"
,"length"
等)或数字键如 1
(例如,"toFixed"
)。相反,你应该直接迭代 Item[K]
。毕竟,我们想要的是 {[M in Owner]: number}
,而不是 {[M in keyof Owner]: number}
。
在我们进行这个更改后,AnotherType
等同于:
type AnotherType = {
type: {
itemTypeA: number;
itemTypeB: number;
} | null;
owner: {
ownerA: number;
ownerB: number;
} | null;
value: {
1: number;
2: number;
} | null;
}
如你所期望的那样。
英文:
You should change your type to something like
type AnotherType = {
[K in keyof Item]: null | {
[M in Item[K]]: number; // <-- no keyof
};
};
where you don't use the keyof
operator inside the nested mapped type.
The problem with M in keyof Item[K]
is that Item[K]
is an indexed access type into the Item
interface, and thus one of ItemType
, Owner
, or Value
, which are already the unions of key-like literal types you want. And since Item[K]
is already keylike, writing keyof Item[K]
is evaluating keys-of-keys. Presumably you don't care about the keys of strings like "ownerA"
(e.g., "toUpperCase"
, "length"
, etc) or the keys of numbers like 1
(e.g., "toFixed"
). Instead you should just iterate over Item[K]
directly. After all, we want {[M in Owner]: number}
, nut {[M in keyof Owner]: number}
.
After we make that change, AnotherType
is equivalent to
type AnotherType = {
type: {
itemTypeA: number;
itemTypeB: number;
} | null;
owner: {
ownerA: number;
ownerB: number;
} | null;
value: {
1: number;
2: number;
} | null;
}
as desired.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论