为什么 Object.keys 在 TypeScript 中不返回 keyof 类型? [英] Why doesn't Object.keys return a keyof type in TypeScript?
问题描述
标题说明了一切 - 为什么 TypeScript 中的 Object.keys(x)
不返回类型 Array
?这就是 Object.keys
所做的,所以似乎 TypeScript 定义文件作者明显疏忽了,没有将返回类型简单地设为 keyof T
.>
我应该在他们的 GitHub 存储库中记录错误,还是直接发送 PR 为他们修复?
当前的返回类型 (string[]
) 是有意的.为什么?
考虑这样的类型:
接口点{x:数量;y:数量;}
你写一些这样的代码:
function fn(k: keyof Point) {如果(k ===x"){console.log("X 轴");} else if (k === "y") {console.log("Y 轴");} 别的 {throw new Error("这是不可能的");}}
让我们问一个问题:
<块引用>在一个类型良好的程序中,对 fn
的合法调用能否命中错误案例?
期望的答案当然是不".但这与 Object.keys
有什么关系?
现在考虑这个其他代码:
interface NamedPoint extends Point {名称:字符串;}const origin: NamedPoint = { name: "origin", x: 0, y: 0 };
请注意,根据 TypeScript 的类型系统,所有 NamedPoint
都是有效的 Point
.
现在让我们写一些代码:
function doSomething(pt: Point) {for (const k of Object.keys(pt)) {//一个有效的调用 iff Object.keys(pt) 返回 (keyof Point)[]fn(k);}}//抛出异常做某事(起源);
我们类型良好的程序只是抛出了一个异常!
这里出了点问题!通过从 Object.keys
返回 keyof T
,我们违反了 keyof T
形成一个详尽列表的假设,因为引用了一个对象并不意味着引用的类型不是值的类型的超类型.
基本上,(至少)以下四件事之一不能为真:
keyof T
是T
键的详尽列表- 具有附加属性的类型始终是其基类型的子类型
- 通过超类型引用为子类型值设置别名是合法的
Object.keys
返回keyof T
丢弃第 1 点使得 keyof
几乎没有用,因为这意味着 keyof Point
可能是某个不是 "x"
的值或 "y"
.
扔掉第 2 点完全破坏了 TypeScript 的类型系统.不是一个选项.
扔掉第 3 点也完全破坏了 TypeScript 的类型系统.
扔掉第 4 点很好,让程序员思考您正在处理的对象是否可能是您认为拥有的事物的子类型的别名.
使此合法但不矛盾的缺失功能"是精确类型,这将允许您声明不受第 2 点约束的新 种类 类型.如果此功能存在,则大概可以使 Object.keys
仅对声明为 的 T
返回 keyof T
准确.
附录:当然是泛型?
评论者暗示如果参数是通用值,Object.keys
可以安全地返回 keyof T
.这仍然是错误的.考虑:
class Holder{值:T;构造函数(参数:T){this.value = arg;}getKeys(): (keyof T)[] {//建议:这应该没问题返回 Object.keys(this.value);}}const MyPoint = { name: "origin", x: 0, y: 0 };const h = new Holder<{ x: number, y: number }>(MyPoint);//值 'name' 包含类型为 'x' 的变量 |'你'const v: "x" |"y" = (h.getKeys())[0];
或者这个例子,它甚至不需要任何显式类型参数:
function getKey(x: T, y: T): keyof T {//建议:这应该没问题返回 Object.keys(x)[0];}const obj1 = { name: "", x: 0, y: 0 };const obj2 = { x: 0, y: 0 };//值name"存在于类型为x"的变量中 |你"const s: "x" |"y" = getKey(obj1, obj2);
Title says it all - why doesn't Object.keys(x)
in TypeScript return the type Array<keyof typeof x>
? That's what Object.keys
does, so it seems like an obvious oversight on the part of the TypeScript definition file authors to not make the return type simply be keyof T
.
Should I log a bug on their GitHub repo, or just go ahead and send a PR to fix it for them?
The current return type (string[]
) is intentional. Why?
Consider some type like this:
interface Point {
x: number;
y: number;
}
You write some code like this:
function fn(k: keyof Point) {
if (k === "x") {
console.log("X axis");
} else if (k === "y") {
console.log("Y axis");
} else {
throw new Error("This is impossible");
}
}
Let's ask a question:
In a well-typed program, can a legal call to
fn
hit the error case?
The desired answer is, of course, "No". But what does this have to do with Object.keys
?
Now consider this other code:
interface NamedPoint extends Point {
name: string;
}
const origin: NamedPoint = { name: "origin", x: 0, y: 0 };
Note that according to TypeScript's type system, all NamedPoint
s are valid Point
s.
Now let's write a little more code:
function doSomething(pt: Point) {
for (const k of Object.keys(pt)) {
// A valid call iff Object.keys(pt) returns (keyof Point)[]
fn(k);
}
}
// Throws an exception
doSomething(origin);
Our well-typed program just threw an exception!
Something went wrong here!
By returning keyof T
from Object.keys
, we've violated the assumption that keyof T
forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.
Basically, (at least) one of the following four things can't be true:
keyof T
is an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keys
returnskeyof T
Throwing away point 1 makes keyof
nearly useless, because it implies that keyof Point
might be some value that isn't "x"
or "y"
.
Throwing away point 2 completely destroys TypeScript's type system. Not an option.
Throwing away point 3 also completely destroys TypeScript's type system.
Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.
The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys
return keyof T
only for T
s which were declared as exact.
Addendum: Surely generics, though?
Commentors have implied that Object.keys
could safely return keyof T
if the argument was a generic value. This is still wrong. Consider:
class Holder<T> {
value: T;
constructor(arg: T) {
this.value = arg;
}
getKeys(): (keyof T)[] {
// Proposed: This should be OK
return Object.keys(this.value);
}
}
const MyPoint = { name: "origin", x: 0, y: 0 };
const h = new Holder<{ x: number, y: number }>(MyPoint);
// Value 'name' inhabits variable of type 'x' | 'y'
const v: "x" | "y" = (h.getKeys())[0];
or this example, which doesn't even need any explicit type arguments:
function getKey<T>(x: T, y: T): keyof T {
// Proposed: This should be OK
return Object.keys(x)[0];
}
const obj1 = { name: "", x: 0, y: 0 };
const obj2 = { x: 0, y: 0 };
// Value "name" inhabits variable with type "x" | "y"
const s: "x" | "y" = getKey(obj1, obj2);
这篇关于为什么 Object.keys 在 TypeScript 中不返回 keyof 类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!