TypeScript Tip #7

Apr 13, 2022

6 mins read

背景

今回もmattさんの記事をまとめていく。
TypeScript Tip #7


概要

オブジェクト中身を順番に処理していく時には、以下のコードを用いることがある。

const myObject = {
  a: 1,
  b: 2,
  c: 3
}

Object.keys(myObject).forEach((key) => {
  console.log(myObject[key])
  // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: number; c: number; }'.
  //  No index signature with a parameter of type 'string' was found on type '{ a: number; b: number; c: number; }'.(7053)
})

しかし、上記のコードではconsole.logで型エラーが発生する。これはmyObject[key]のkeyが存在しないオブジェクトのキーを指定しまう可能性があるとして、エラーが発生している。
参考記事:TypeScriptでブラケット記法を使うときにハマったこと

今回は、この場合の対応策を考えていく。


本題

実際のサンプルコードは以下になる。

const myObject = {
    a: 1,
    b: 2,
    c: 3
}

const objectKeys = <Obj>(obj: Obj): (keyof Obj)[] => {
  return Object.keys(obj) as (keyof Obj)[];
}

objectKeys(myObject).forEach((key) => {
  console.log(myObject[key]);
})
  1. 今回はobjectKeys関数を作成し、引数objを型推論させたObjを作成する。 例えば、上記のmyObject定数を型推論させると以下になる。
const myObject = {
    a: 1,
    b: 2,
    c: 3
}

type MyObject = typeof myObject
// type MyObject = {
//   a: number;
//   b: number;
//   c: number;
// }

よって、上記でいうMyObjectが型Objになる。また、ジェネリクスで定義しているため、関数全体で使用できる型になっている。

  1. 二ヶ所でkeyof Objを使用しているが、これはオブジェクトのkeyを切り取った、ユニオンのリテラル型になる。 例えば、MyObjectの型を定義すると以下になる。
type KeyOf = keyof MyObject

🔽

type KeyOf = keyof {
    a: number;
    b: number;
    c: number;
}

🔽

type KeyOf = "a" | "b" | "c"
  1. ここまでで、定数myObjectを型定義をあてはまると、以下になる
objectKeys(myObject)

const objectKeys = <Obj>(obj: Obj): (keyof Obj)[] => {
  return Object.keys(obj) as (keyof Obj)[];
}

🔽

type Obj = {
    a: number;
    b: number;
    c: number;
}
const objectKeys = <Obj>(obj: Obj): ("a" | "b" | "c")[] => {
    return Object.keys(obj) as ("a" | "b" | "c")[];
    // 型アサーションでa, b, cのどれかが含まれている配列であることを宣言している。
}

console.log(Object.keys(myObject))
//  ["a", "b", "c"] 
  1. 上記の配列を作成することによって、foreach分の引数の値に型定義がきちんとされ、オブジェクトのキーとしてきちんと利用することができる。
objectKeys(myObject).forEach((key) => {
  // ここのkeyは以下の様に、型定義が聞いてくれる。
  // key: "a" | "b" | "c"
  console.log(myObject[key]);
})

まとめ

今回はforEach分の例にしているが、通常のオブジェクトの操作をしている時に出てくる対処策は以下の記事にかいていたので、
併せて確認するとさらに勉強になった。 参考記事:TypeScriptでブラケット記法を使うときにハマったこと


参考文献

TypeScript Tip #7
TypeScriptでブラケット記法を使うときにハマったこと