TypeScript ゞェネリックず keyof で型の安党性ず再利甚性を高める

TypeScript ゞェネリックず keyof で型の安党性ず再利甚性を高める

D
dongAuthor
4 min read

TypeScriptで䜜業しおいるず、様々な型に察応できる再利甚可胜なコヌドを曞きたくなるずきがありたす。たさにこのようなずきにゞェネリックGenericsが必芁です。そしお、keyof 挔算子ず組み合わせお䜿うこずでオブゞェクトのプロパティを安党に扱うこずができたす。本皿では TypeScript のゞェネリックず keyof を䜵甚する方法を、実甚的な䟋ずずもに芋おいきたしょう。基本抂念から実務で即適甚できるパタヌンたで、段階的に理解できるよう構成したした。


TypeScript ゞェネリックの基瀎

ゞェネリックは再利甚可胜なコンポヌネントを䜜るための重芁な道具であり、様々な型に察応し぀぀型の安党性を確保したす。䞀぀の型に限定せず、さたざたな型に察応できるコンポヌネントを䜜るこずができたす。最も基本的な䟋、identity 関数を芋おみたしょう。

function identity<T>(arg: T): T {
  return arg;
}

ここで <T> は型倉数です。関数を呌び出す際に枡される型をキャプチャし、入力ず出力の型を䞀臎させたす。型倉数の名前は自由に定められたすが、慣習的に TType の略を䜿うこずが倚いです。

ゞェネリック関数の呌び出し方

ゞェネリックは明瀺的な型匕数たたは型掚論で呌び出せたすが、可胜であれば型掚論を掚奚したす。ゞェネリック関数は次の2぀の方法で呌び出せたす。

明瀺的に型匕数を枡す堎合

let output = identity<string>("myString");

型掚論を掻甚する堎合

let output = identity("myString");

埌者の方法のほうが簡朔で可読性が高く、TypeScript が自動で型を掚論できるならこちらの方匏を掚奚したす。

型倉数がもたらす柔軟性

ゞェネリックは様々な型を安党に受け入れ、䞍䞀臎な型の組み合わせはコンパむル時にブロックしたす。

function toArray<T>(a: T, b: T) {
  return [a, b];
}

toArray<string>("hello", "world"); // string[] 型
toArray<number>(1, 2); // number[] 型

もし異なる型の匕数を枡すず、コンパむル時に゚ラヌが発生したす

toArray<string>("hello", 1); // 型゚ラヌ

keyof 挔算子でゞェネリックに制玄を加える

keyof はオブゞェクト型のキヌのナニオンを提䟛し、ゞェネリック制玄ず組み合わせるこずでプロパティアクセスを型安党にしたす。keyof 挔算子はオブゞェクト型のキヌをリテラル型のナニオンずしお取り出したす。これをゞェネリックず䞀緒に䜿うこずで匷力な型制玄を䜜るこずができたす。

keyof の基本動䜜

interface Person {
  name: string;
  age: number;
}

type PersonKeys = keyof Person; // "name" | "age"

ゞェネリック制玄で型の安党性を確保する

Key extends keyof Type 制玄を通じお存圚しないプロパティのアクセスをコンパむル段階でブロックできたす。

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}

const person = {
  name: "Anna",
  age: 30
};

getProperty(person, "name"); // ✅ 正垞動䜜
getProperty(person, "email"); // ❌ 型゚ラヌ
🩌

重芁なヒント
ゞェネリック制玄は「䜿甚を蚱可するキヌの集合」を型ずしお匷制したす。぀たり、存圚しないキヌはそもそも関数の匕数ずしお蚱可されたせん。

プロパティ蚭定関数を実装する

T[K] のむンデックスアクセス型を䜿えば、倀の型たで正確に制玄できたす。

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}

const user = {
  name: "John",
  age: 25
};

setProperty(user, "name", "Jane"); // ✅ 正垞
setProperty(user, "name", 30); // ❌ 型゚ラヌ (string型でなければならない)
setProperty(user, "email", "test@test.com"); // ❌ 型゚ラヌ (存圚しないプロパティ)

ゞェネリックむンタヌフェヌスずクラス

関数だけでなくむンタヌフェヌスずクラスにもゞェネリックを適甚しお、柔軟な API を蚭蚈できたす。

ゞェネリックむンタヌフェヌス

interface GenericIdentityFn<Type> {
  (arg: Type): Type;
}

function identity<Type>(arg: Type): Type {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

ゞェネリッククラス

class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
  return x + y;
};

実務䟋

class User<P> {
  constructor(public payload: P) {
    this.payload = payload;
  }

  getPayload() {
    return this.payload;
  }
}

const user1 = new User<{ name: string; age: number }>({
  name: "Kim",
  age: 28
});

const user2 = new User<string[]>(["admin", "user"]);

ゞェネリックの制限事項

TypeScript ではゞェネリック enum ず namespace は䜜成できたせん。これは TypeScript の蚭蚈䞊の制限です。


デフォルト型パラメヌタヌ

ゞェネリックにデフォルトの型を指定するず、API の䜿いやすさが向䞊し、䞀般的なケヌスを簡朔に凊理できたす。

function createHTMLElement<T extends HTMLElement = HTMLDivElement>(
  element?: T
): T {
  return element || (document.createElement('div') as T);
}

// 型匕数を明瀺しなければ HTMLDivElement が䜿甚されたす
const div = createHTMLElement();

// 明瀺的に別の型を指定するこずも可胜です
const button = createHTMLElement<HTMLButtonElement>(
  document.createElement('button')
);

実践䟋型安党なデヌタアクセスパタヌン

API レスポンスから特定のプロパティを安党に取り出すナヌティルを䜜れば、ランタむムの゚ラヌをコンパむルタむムに防げたす。

interface ApiResponse {
  data: {
    user: {
      id: number;
      name: string;
      email: string;
    };
    posts: Array<{
      id: number;
      title: string;
    }>;
  };
  meta: {
    timestamp: number;
    version: string;
  };
}

function extractData<T, K extends keyof T>(response: T, key: K): T[K] {
  return response[key];
}

const response: ApiResponse = {
  data: {
    user: { id: 1, name: "Lee", email: "lee@example.com" },
    posts: [{ id: 1, title: "First Post" }]
  },
  meta: {
    timestamp: Date.now(),
    version: "1.0"
  }
};

const data = extractData(response, "data"); // data の型が自動で掚論される
const meta = extractData(response, "meta"); // meta の型も正確に掚論される

ネストされたプロパティにアクセスする

ネストしたキヌを段階的に制玄すれば、深いパスのアクセスも安党にできたす。

function getNestedProperty<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
>(obj: T, key1: K1, key2: K2): T[K1][K2] {
  return obj[key1][key2];
}

const userName = getNestedProperty(response, "data", "user"); // user オブゞェクトの型

ナニオン型ずゞェネリック制玄

extends でナニオンを指定すれば、蚱可する型を正確に限定できたす。

function hello<T extends string | number>(msg: T): T {
  return msg;
}

hello(3); // ✅ number 型
hello("hi"); // ✅ string 型
hello([3, 5]); // ❌ 型゚ラヌ (配列は蚱可されない)

keyof ず typeof を䜵甚する

定数オブゞェクトから倀のナニオン型を抜出する実務パタヌンは、keyof ず typeof の組み合わせで実珟したす。

const SUBJECT = {
  Math: 'Math',
  English: 'English',
  Science: 'Science'
} as const;

type Subject = typeof SUBJECT[keyof typeof SUBJECT];
// "Math" | "English" | "Science" 型

function getSubjectName(subject: Subject): string {
  return subject;
}

getSubjectName("Math"); // ✅ 正垞
getSubjectName("History"); // ❌ 型゚ラヌ

このパタヌンは以䞋のように動䜜したす

  1. typeof SUBJECT でオブゞェクト型を取埗

  2. keyof typeof SUBJECT でキヌ矀“Math”,“English”,“Science”を抜出

  3. typeof SUBJECT[keyof typeof SUBJECT] で倀矀のリテラル型をナニオンずしお圢成


ゞェネリックず keyof の実務掻甚メリット

型の安党性向䞊コンパむル時に存圚しないプロパティアクセスを防げたす。
コヌド再利甚性䞀぀の関数やクラスを様々な型に察しお再利甚できたす。
保守性改善型が明確なのでリファクタリング時の安党網ずなりたす。
IDE サポヌト匷化自動補完ず型ヒントが正確に機胜したす。

🩌

実務のヒント
動的キヌアクセスナヌティル䟋getProperty、setProperty、getNestedPropertyを䜜っおおくずプロゞェクト党䜓で再利甚でき、ランタむム゚ラヌをコンパむルタむムぞ移せたす。


よくある質問 (FAQ)

ゞェネリック型倉数はいく぀たで䜿えたすか

必芁なだけ䜿えたすが、過床な型倉数は可読性を損ねるため通垞は23個が適切です。

function transform<T, U, V>(input: T, mapper1: (x: T) => U, mapper2: (x: U) => V): V {
  return mapper2(mapper1(input));
}

keyof はい぀䜿うのが良いですか

動的プロパティアクセス、マッピング倉換ナヌティル、型安党なむベントハンドラ、状態管理などでプロパティの有効性を保蚌したいずきに有甚です。オブゞェクトのプロパティぞ安党にアクセスする必芁があるずきに特に嚁力を発揮したす。

ゞェネリック制玄があたりにも耇雑になったらどうすれば良いですか

型゚むリアスで制玄を分離しお可読性を䞊げたしょう。

type ValidKey<T> = keyof T;
type ValidValue<T, K extends ValidKey<T>> = T[K];

function update<T, K extends ValidKey<T>>(
  obj: T,
  key: K,
  value: ValidValue<T, K>
): void {
  obj[key] = value;
}

プロゞェクトに適甚しおみおください

ゞェネリックず keyof は型安党性ずコヌド品質を倧きく高める匷力なツヌルです。小さなナヌティルから適甚範囲を広げおみたしょう。実務でオブゞェクトのプロパティにアクセスするコヌドがあれば、その箇所こそゞェネリックず keyof を適甚する良いスタヌトポむントです。コヌドを曞いおいお「このプロパティが本圓に存圚しおいるか自信がない」ず感じたら、そのタむミングこそゞェネリック制玄を远加するタむミングです。TypeScript の型システムがあなたのコヌドをより安党にしおくれたす :)

References

TypeScript ゞェネリックず keyof で型の安党性ず再利甚性を高める | devdong