Closed4

TypeScriptで keyof T が string | number | symbol に推論される理由の調査

https://zenn.dev/jiftechnify/articles/2489f4103918a2 の中で問題になった、以下の挙動について理由を調べた。

  • Record<string, ...>型の変数に数値をキーに持つオブジェクトを代入できる
  • keyof Tが、 Tの具体的な型が確定しない状況で string | nunber | symbolと推論される

結論からいうと、TypeScript 2.9で入った仕様だった。さらにいえば、このリリースノートに(上記の記事で紹介したものよりも遥かに単純な)対処法も載っていた…。

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#support-number-and-symbol-named-properties-with-keyof-and-mapped-types

上記リリースノートから関連する部分を引用。

Changes include:

  • An index type keyof T for some type T is a subtype of string | number | symbol.

Given an object type X, keyof X is resolved as follows:

  • If X contains a string index signature, keyof X is a union of string, number, and the literal types representing symbol-like properties, otherwise
  • keyof Tが、 Tの具体的な型が確定しない状況でstring | nunber | symbolと推論される

これについてはそのものずばりの記述がある。

  • Record<string, T>型の変数に数値をキーに持つオブジェクトを代入できる

これについては直接的な言及がない気がするが、stringのインデックスシグネチャを持つ場合(Record<string, T>{ [key: string]: T }のエイリアスなので、これに該当)にkeyof Xstringだけでなくnumberも含むunionになるという仕様から逆に考えると、意図通りの挙動だと考えられる。

JavaScriptの { 1: 'a' }のように書くと{ '1': 'a' }のように数値が勝手に文字列に変換される仕様と互換性を持たせるためにこうしたんだろうか? (ちょっと腑に落ちない)

時間があるときに、この仕様変更に関わるPRを読んでみる?

https://github.com/microsoft/TypeScript/pull/23592

リリースノートで紹介されている、keyof Tの型推論結果からstring以外を除く方法。

This is a breaking change; previously, the keyof operator and mapped types only supported string named properties. Code that assumed values typed with keyof T were always strings, will now be flagged as error.
(略)
Recommendations

  • If your functions are only able to handle string named property keys, use Extract<keyof T, string> in the declaration:
function useKey<T, K extends Extract<keyof T, string>>(o: T, k: K) {
  var name: string = k; // OK
}

この方法で記事の方法を置き換えられることを確認。なんでこんなに簡単な方法に気づかなかったんだろう…

このスクラップは2021/12/14にクローズされました
作成者以外のコメントは許可されていません