TypeScript で、ブラケット記法を使ったプロパティアクセスを型安全に行う

2022/10/21に公開

はじめに

ブラケット記法とは、オブジェクトのプロパティに対して変数でアクセスする時に使用する記法です。

let obj = {
  t: "text"
  x: 0,
  y: 10,
};

obj["x"] = 100;
let k = "text";
obj[k] = "updated"

変数が使えるので、汎用的な関数を作る時等にとても便利なのですが、TypeScript では代入時に型チェックが入ってエラーが出てしまいます。

これを型安全に行える関数を作りました。

実装

// 使用する型
interface TestObject {
  text: string;
  num: number;
  complex: {
    version: string;
  };
}

// 操作対象のオブジェクト
const obj: TestObject = {
  text: "OK",
  num: 100,
  complex: {
    version: "0.0.1",
  },
};

// 引数の型指定にgenericを使用
const updateValue = <K extends keyof TestObject, V extends TestObject[K]>(
  key: K,
  value: V
) => {
  obj[key] = value;
};

updateValue("text", "hello");
updateValue("text", 123); //型エラー
updateValue("num", 123);

ちょっと解説

TypeScript が型安全であると判定してくれれば良いので、Generic を使って代入できる値を限定しました。
これによって、不測の値が入った時にエラーを吐いてくれるようにもなります。

const updateValue = <K extends keyof TestObject, V extends TestObject[K]>(
  key: K,
  value: V
) => {
  obj[key] = value;
};

K extends keyof TestObject

キーとなる値を TestObject 内に存在するプロパティ名に限定します。

V extends TestObject[K]

代入できる値は、TestObject.(プロパティ名) の型に一致している必要があるので、先に定義した K を使用して TestObject から型を抜き出しています。

代入する対象も動的に変更する

先の実装では代入対象のオブジェクトを固定していましたが、それも動的にする事も可能です。
対象となるオブジェクトも引数に加えて、TestObject の部分を Generic にする事で実現出来ます。

const updateValue = <T, K extends keyof T, V extends T[K]>(
  target: T,
  key: K,
  value: V
) => {
  target[key] = value;
};

おわりに

きっと同じ事をしている人が世の中に何人もいるとは思うのですが、定義済みの interface から動的にプロパティ名を抜き出す方法と合わせたものが調べても出てこなかったので記事にしました。
何かの役に立てば幸いです。

Discussion