为什么 Object.keys 在 TypeScript 中不返回 keyof 类型? [英] Why doesn't Object.keys return a keyof type in TypeScript?

查看:16
本文介绍了为什么 Object.keys 在 TypeScript 中不返回 keyof 类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

标题说明了一切 - 为什么 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 形成一个详尽列表的假设,因为引用了一个对象并不意味着引用的类型不是值的类型的超类型.

基本上,(至少)以下四件事之一不能为真:

  1. keyof TT
  2. 键的详尽列表
  3. 具有附加属性的类型始终是其基类型的子类型
  4. 通过超类型引用为子类型值设置别名是合法的
  5. 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 NamedPoints are valid Points.

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:

  1. keyof T is an exhaustive list of the keys of T
  2. A type with additional properties is always a subtype of its base type
  3. It is legal to alias a subtype value by a supertype reference
  4. Object.keys returns keyof 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 Ts 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆