1. ホーム
  2. typescript

[解決済み] TypeScriptでObject.keysがkeyof型を返さないのはなぜですか?

2022-09-20 13:53:01

質問

タイトルがすべてを物語っている - なぜそうしないのか Object.keys(x) という型を TypeScript で返せるのはなぜか? Array<keyof typeof x> ? それは Object.keys がそうであるように、TypeScript の定義ファイルの作成者が戻り値の型を単純に keyof T .

彼らの GitHub リポにバグを記録するべきか、それとも彼らのために修正する PR を送るべきか?

どのように解決するのですか?

現在の戻り値型( string[] ) は意図的なものです。なぜでしょうか?

このような何らかの型を考えてみましょう。

interface Point {
    x: number;
    y: number;
}

このようなコードを書くのです。

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");
    }
}

質問してみましょう。

よく型付けされたプログラムにおいて、合法的に fn をエラーケースにヒットさせることができますか?

欲しい 答えはもちろん、「いいえ」です。しかし、このことが Object.keys ?

では、次のように考えてみましょう。 その他 のコードを考えてみましょう。

interface NamedPoint extends Point {
    name: string;
}

const origin: NamedPoint = { name: "origin", x: 0, y: 0 };

TypeScriptの型システムに従って、すべての NamedPoint は有効な Point s.

では、次のように書いてみましょう。 をもう少し書いてみましょう。 :

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);

よく型付けされたプログラムが例外を投げました!

ここで何かが間違っていたのです! を返すことで keyof T から Object.keys という仮定を破っていることになります。 keyof T は網羅的なリストを形成するという仮定に違反しています。なぜなら、オブジェクトへの参照を持つことは 型の参照を持つことは のスーパータイプでないことを意味するからです。 のスーパータイプではありません。 .

基本的に、以下の4つのうち(少なくとも)1つは真であるはずがない。

  1. keyof T のキーの網羅的なリストです。 T
  2. 追加のプロパティを持つ型は、常にその基本型のサブタイプである
  3. スーパータイプの参照によってサブタイプの値を別名にすることは合法です。
  4. Object.keys は返す keyof T

ポイント1を捨てると keyof はほとんど無意味です。 keyof Point ではない何らかの値である可能性があるからです。 "x" または "y" .

ポイント2を捨てると、TypeScriptの型システムが完全に破壊されます。オプションではありません。

3を捨てると、TypeScriptの型システムが完全に破壊されます。

4は問題なく、プログラマは自分が扱っているオブジェクトが、自分が持っていると思っているオブジェクトのサブタイプの別名である可能性があるかどうかを考えることになる。

これを実現するための"missing feature"は 合法でありながら矛盾しない 正確なタイプ を宣言することで、新しい 種類 を宣言することができます。この機能があれば、おそらくは Object.keys を返す keyof T に対してのみ T として宣言された 正確には .


追記:確かにジェネリックですが?

コメンテーターは次のようにほのめかしています。 Object.keys は安全に keyof T を安全に返すことができます。これはまだ間違っています。考えてみてください。

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];

またはこの例では、明示的な型引数さえ必要ありません。

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);