为什么Object.keys不返回TypeScript中的keyof类型?

Rya*_*ugh 32 typescript

标题说明了一切-为什么Object.keys(x)在TypeScript 中不返回类型Array<keyof typeof x>?就是这样Object.keys做的,因此对于TypeScript定义文件作者来说,似乎很明显的疏忽是不将返回类型简单地设为keyof T

我应该在他们的GitHub存储库上记录错误,还是继续发送PR为其进行修复?

Rya*_*ugh 36

当前的返回类型(string[])是有意的。为什么?

考虑这样的某种类型:

interface Point {
    x: number;
    y: number;
}
Run Code Online (Sandbox Code Playgroud)

您编写如下代码:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们问一个问题:

在类型良好的程序中,可以通过法律电话fn打到错误案例吗?

所希望的答案是,当然是“否”。但是,这与什么有关Object.keys呢?

现在考虑其他代码:

interface NamedPoint extends Point {
    name: string;
}

const origin: NamedPoint = { name: "origin", x: 0, y: 0 };
Run Code Online (Sandbox Code Playgroud)

请注意,根据TypeScript的类型系统,所有NamedPoint都有效Point

现在让我们再编写一些代码

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);
Run Code Online (Sandbox Code Playgroud)

我们类型良好的程序只是引发了异常!

这里出问题了!通过返回keyof TObject.keys,我们已经违反了这一假设keyof T形成一个详尽的清单,因为有一个对象的引用并不意味着引用的类型不是的超类型的值的类型

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

  1. keyof T 是以下各项的详尽清单: T
  2. 具有附加属性的类型始终是其基本类型的子类型
  3. 通过超类型引用为子类型值别名是合法的
  4. Object.keys 退货 keyof T

丢掉点1 keyof几乎没有用,因为它暗示keyof Point可能是不等于"x"或的一些值"y"

扔掉点2会完全破坏TypeScript的类型系统。没有选择。

扔掉第3点也会完全破坏TypeScript的类型系统。

丢掉第4点就可以了,这使程序员,您可以考虑所处理的对象是否可能是您所拥有的事物的子类型的别名。

“丢失的功能”,使这个合法的,但并不矛盾精确的类型,这样可以让你声明一个新类型的,这是不符合点#2。如果存在此功能,则可能只对声明为precision的 s Object.keys返回。keyof TT

  • 正如 arthem 还指出的那样,混淆来自这样一个事实,即 10 次中有 9 次你最终会以某种方式对 `keyof T` 使用类型断言来对 `keys` 的结果做任何有用的事情。您可能会争辩说,最好明确说明它,以便您更加了解您所承担的风险,但可能 9/10 开发人员只会添加类型断言,而不会意识到您突出显示的问题。 (5认同)
  • 但是,在常见情况下,将点3排除在外,例如可以推断出T并保证其精确:`const f:&lt;T&gt;(t:T)=&gt; void =(t)=&gt; { Object.keys(t).forEach(k =&gt; t [k])}`。我的代码中有很多类似的地方,我确实希望`Object.keys()`返回(keyof T)[]。 (4认同)
  • 嗯,那么除了 TypeScript 和类型系统的伟大理论之外,不可能将“Object.keys”与代表接口的对象一起使用?我们被迫创建毫无意义的辅助函数来取代我们语言的基础设施? (4认同)
  • 如果有人真的因为将 Object.keys(foo) 类型转换为 Array&lt;keyof typeof foo&gt; 而搞砸了,其中 Object.keys 的运行时值实际上包含比编译时已知的更多的键,那么很多人都会喜欢请参阅此代码作为示例。请分享 (4认同)
  • 通常,这仅用于能够循环遍历对象,但这使得不可能,因为 ```Object.keys(product).forEach((key) =&gt; { // do some with Product[key] but indicates错误 '具有任何类型,因为字符串不能用于查询...' });``` 将导致错误。 (3认同)
  • 为什么`Object.keys&lt;T&gt;(c extends T obj)`不能简单地过滤返回T键的obj(类型c)上的键? (2认同)
  • 我认为这个例子很好,但不太令人满意。 (2认同)

Sep*_*eed 15

这是此类问题在谷歌上的热门搜索,因此我想分享一些关于前进的帮助。

这些方法很大程度上取自各个问题页面上的长时间讨论,您可以在其他答案/评论部分找到这些页面的链接。

所以,假设你有一些这样的代码

const obj = {};
Object.keys(obj).forEach((key) => {
  obj[key]; // blatantly safe code that errors
});
Run Code Online (Sandbox Code Playgroud)

以下是一些前进的方法:

  1. 如果您不需要键而实际上只需要值,请使用.entries()or.values()而不是迭代键。

    const obj = {};
    Object.values(obj).forEach(value => value);
    Object.entries(obj).forEach([key, value] => value);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建一个辅助函数:

    function keysOf<T extends Object>(obj: T): Array<keyof T> {
      return Array.from(Object.keys(obj)) as any;
    }
    
    const obj = { a: 1; b: 2 };
    keysOf(obj).forEach((key) => obj[key]); // type of key is "a" | "b"
    
    Run Code Online (Sandbox Code Playgroud)
  3. 重新转换你的类型(这对于不必重写太多代码有很大帮助)

    const obj = {};
    Object.keys(obj).forEach((_key) => {
      const key = _key as keyof typeof obj;
      obj[key];
    });
    
    Run Code Online (Sandbox Code Playgroud)

其中哪一项最无痛很大程度上取决于您自己的项目。

  • 我最近对此感到头疼,并想在堆上再扔一个选项:转换为 [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/全局对象/地图)。转换为 Map 来支持旧代码是一个巨大的麻烦,但如果你正在编写新的东西,那么使用它就像“Object.keys”模式一样非常容易,我——如果你正在阅读这篇文章,那么使用它可能也是如此—— - 习惯了使用。`const myMap: Map&lt;SomeType, SomeOtherType&gt; = new Map()`,然后用 `myMap.forEach((val, key) =&gt; {... and TypeScript is happy here ...})` 循环它 (7认同)

rat*_*ray 5

在您确信正在使用的对象中没有额外属性的情况下,对于解决方法,您可以执行以下操作:

const obj = {a: 1, b: 2}
const objKeys = Object.keys(obj) as Array<keyof typeof obj>
// objKeys has type ("a" | "b")[]
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以将其提取到函数中:

const getKeys = <T>(obj: T) => Object.keys(obj) as Array<keyof T>

const obj = {a: 1, b: 2}
const objKeys = getKeys(obj)
// objKeys has type ("a" | "b")[]
Run Code Online (Sandbox Code Playgroud)

作为奖励,这是Object.entriesGitHub 问题中提取的关于为什么这不是默认值的上下文

type Entries<T> = {
  [K in keyof T]: [K, T[K]]
}[keyof T][]

function entries<T>(obj: T): Entries<T> {
  return Object.entries(obj) as any;
}
Run Code Online (Sandbox Code Playgroud)