1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

りかいが深まる TypeScript の動的型付け:in と keyof の実践的活用法

Posted at

はじめに

オブジェクトのkeyを動的に設定するための書き方として、インデックスシグネチャというものがあります。
そのインデックスシグネチャを定義する際、以下のような書き方があることを知りました。

type User<T> = {
  [k in keyof T]: string;
};

この記事ではintypeofのそれぞれについて触れ、上記の型が表す意味を解説します。

inの役割

inはインデックスシグネチャに使用される構文で、Mapped Typesと呼ばれる型を構築します。

インデックスシグネチャ

そもそもインデックスシグネチャとはオブジェクトのkeyをあらかじめ宣言することなく、動的にあとから追加できるようにするものです。

インデックスシグネチャのおさらい
type User = {
  [k: string]: string;
};

const useData: User = {
  name: "Kevin",
  country: "America",
};

User型はstring型のkeyとstring型の値を持つオブジェクトとして定義しています。
そしてuserDataUser型となっており、型の定義に合致するプロパティであれば自由に追加ができます。

しかし、あまりにも自由に追加されるのも困ることがあります。
ある程度制限をかけたいときに使えるのがinです。

基本のin

inの基本
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

type ApiProcess = {
  [k in HttpMethod]: () => void;
};

const apiProcess: ApiProcess = {
  GET: () => {
    console.log("get");
  },
  POST: () => {
    console.log("post");
  },
  PUT: () => {
    console.log("put");
  },
  DELETE: () => {
    console.log("delete");
  },
};

まず、HttpMethodというユニオン型を用意しました。

そして、インデックスシグネチャの中で、inのあとにユニオン型を指定しています。

これにより、ApiProcessという型はHttpMethodに定義された4つの値をキーに持つオブジェクトであるというふうに定義できました。

inを使わない場合、ApiProcessは以下のように書く必要があります。

type ApiProcess = {
  GET: () => void;
  POST: () => void;
  PUT: () => void;
  DELETE: () => void;
};

同じようなことを繰り返し書いているので、冗長に見えます。

inを使うことで、設定したいkeyを制限しつつ、同じことを繰り返し書かずともオブジェクトの型を定義できます。

その他のin

上記で確認したのはMapped TypesというTypeScriptにおけるinでしたが、他にもinを使った構文は存在します。

ややこしいので、念の為整理しておきます。

for...in

ループ処理で使用する構文です。

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

for (const key in obj) {
    console.log(`${key}: ${obj[key]}`);
}

判定のin

指定した値がオブジェクトのプロパティに存在するかどうかを判定します。

const animal = {
  dog: "わんわん",
  cat: "にゃーにゃー",
};
if ("dog" in animal) {
  console.log(animal.dog);
}

これらはもともとJavaScriptにも用意されている構文です。
今回確認したMapped Typesとは関係がないので注意してください。

keyofの役割

過去の記事で一度keyofについては触れていますが、改めて確認します。

keyof演算子はオブジェクトのプロパティだけをまとめて取得して、リテラルのユニオン型として設定することができます。

keyofの基本
type Person = {
    name: string;
    age: number;
    address: string;
}

type PersonKeys = keyof Person;
// type PersonsKeys = 'name' | 'age' | 'address'と定義するのと同じ

これにより、オブジェクトのプロパティを動的に取得することができます。

keyofの使用例
type Person = {
  name: string;
  age: number;
  address: string;
};

const user: Person = {
  name: "Kevin",
  age: 28,
  address: "address",
};

const printPerson = (param: keyof Person) => {
  console.log(user[param]);
};

in keyof Tの意味

ここまでくれば、何を表しているかがわかるかと思います。

inはユニオン型をもとにオブジェクトのkeyを決定します。
そして、keyofはオブジェクトのプロパティからユニオン型を生成します。

つまり、あるオブジェクトのプロパティと同じkeyを持つことを指定できる書き方がin keyof Tになります。

in keyof Tの使用例

うまく使うことで、以下のような動的なバリデーションの処理を作成することができます。

interface FormData {
  username: string;
  age: number;
  email: string;
  password: string;
}

// バリデーションルール用の型を定義
// Tのプロパティの型をアロー関数の引数の型として設定
type ValidationRules<T> = {
  [k in keyof T]: (value: T[k]) => string | null;
};

// バリデーションルールのオブジェクトを作成
// バリデーションルール用の型のTにはFormDataが設定される
const validationRules: ValidationRules<FormData> = {
  username: (value) =>
    value.length < 3 ? "ユーザー名は3文字以上である必要があります" : null,
  age: (value) => (value > 100 ? "年齢は100未満で入力してください" : null),
  email: (value) =>
    /@/.test(value) ? null : "有効なメールアドレスを入力してください",
  password: (value) =>
    value.length < 8 ? "パスワードは8文字以上である必要があります" : null,
};

// フォームにバリデーションを実施する処理
// 戻り値の型や、エラー情報の型にもin keyofを使用
function validateForm<T>(
  data: T,
  rules: ValidationRules<T>
): { [k in keyof T]?: string } {
  const errors: Partial<{ [k in keyof T]: string }> = {};

  // ここでのinはループ。Mapped Typesではない
  for (const key in data) {
    if (key in rules) {
      const error = rules[key](data[key]);
      if (error) {
        errors[key] = error;
      }
    }
  }

  return errors;
}

まとめ

in keyofを使うことで、型安全を保ちながら柔軟な処理を書くことができました。
一見よくわからない処理でも、一つずつ紐解いていくことで何をしているのかを明らかにすることができます。

もっと型に関連する知識を沢山身につけていきたいと思いました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?