たれぱんのびぼーろく

わたしの備忘録、生物学とプログラミングが多いかも

TypeScriptはdot記法とbracket記法で異なる型推論をおこなう(オブジェクトプロパティアクセサー)

JavaScript/TypeScriptではオブジェクトのプロパティにアクセスする表現をプロパティアクセサーという。
プロパティアクセサーには2つの記法、dot記法とbracket記法がある。

const obj = {
  a: 2
};

// dot notation
obj.a = 1;
const dot = obj.a;

// bracket notation
obj["a"] = 1;
const bracket = obj["a"];

これらはECMAScriptによって規定されている。

Properties are accessed by name, using either the dot notation ...
or the bracket notation
ECMAScript 10th 12.3.2 Property Accessors

参考: MDN web docs - プロパティアクセサー

TypeScript 型推論

ではTypeScriptの型推論はこれらをどう調理するのか。
検証してみた。
各記法でオブジェクトプロパティへアクセスし、型推論結果をコメントに記した。

version: TypeScript 3.7.3 (VSCode 1.41.0)

type Side = "A" | "B";

// deterministic
const side = "A";
const obj = {
  A: 1,
  B: "hello"
};
const dot = obj.A; // number
const bracketRaw = obj["A"]; // number
const bracketVar = obj[side]; // number
//// undefined property
const side2 = "C";
const dot2 = obj.C; // ERROR: Property 'C' does not exist on type '{ A: number; B: string; }'. ts(2339)
obj.C = 1; // ERROR: Property 'C' does not exist on type '{ A: number; B: string; }'. ts(2339)
const bracketRaw2 = obj["C"]; // any
const bracketVar2 = obj[side2]; // any
obj["C"] = 1; // no error

// undeterministic
function tester(side: Side) {
  const obja = {
    A: 1,
    B: "hello"
  };
  const dot = obja.A; // number
  const bracketRaw = obja["A"]; // number
  const bracketVar = obja[side]; // string | number
}

TSはbracket記法でも賢く型推論してくれる。
そしてdot記法とbracket記法で異なる推論をおこなっている。

dot記法の場合、未定義 (undefinedな) プロパティへのアクセスはコンパイラによるErrorとして扱う.
bracket記法の場合、未定義 (undefinedな) プロパティへのアクセスはany型として扱う.

伝統的にbracket記法をdynamic propertyとして利用するのでundefinedにアクセスできないと困る、という背景なのかと思う。
dynamic propertyでも事前に型定義をしておけばきちんと型推論される (tester内でobja[side]が正しくstring | numberに推論されている) のでとても助かる。

結言

TypeScriptはオブジェクトプロパティアクセサーのdot記法とbracket記法で異なる型推論をおこなう。
どちらの記法でも充分な型推論をおこなってくれるが、undefined propertyをdot記法はエラー、bracket記法はanyとして扱う。
TypeScriptはいいぞ。