TypeScript | 基礎文法:オブジェクト基礎 – 型安全なオブジェクト更新

TypeScript
スポンサーリンク

「型安全なオブジェクト更新」とは何を守ることか

まずゴールをはっきりさせます。
型安全なオブジェクト更新とは、「オブジェクトを書き換えるときに、存在しないプロパティ名を書いたり、間違った型の値を入れたりしないよう、型で守られた状態で更新すること」です。

JavaScriptだと、こういうミスがそのまま通ってしまいます。

const user = { name: "Taro", age: 20 };

user.nmae = "Jiro";   // スペルミス
user.age = "twenty";  // 型のミス
TypeScript

TypeScript では、これをコンパイル時に止めたい。
そのために「オブジェクトの型」と「更新の仕方」をちゃんと設計していく、という話です。


まずは基本:型付きオブジェクトの単純な更新

プロパティの更新は型チェックされる

シンプルな例からいきます。

type User = {
  name: string;
  age: number;
};

let user: User = {
  name: "Taro",
  age: 20,
};

user.name = "Jiro";  // OK
user.age = 21;       // OK

user.age = "21";     // エラー: Type 'string' is not assignable to type 'number'.
user.nmae = "Ken";   // エラー: Property 'nmae' does not exist on type 'User'.
TypeScript

ここで起きていることは直感的です。

name は string だから string しか入れられない。
age は number だから number しか入れられない。
User に存在しないプロパティ名を書いたらエラー。

つまり、「型をちゃんと定義しておけば、プロパティ更新の時点でかなり守られる」ということです。


スプレッド構文で「新しいオブジェクトとして更新」する

破壊的更新と非破壊的更新

さっきのように user.name = ... と書くのは「元のオブジェクトを書き換える」更新です。
一方で、React などではよく「元のオブジェクトはそのままにして、新しいオブジェクトを作る」書き方をします。

const user: User = {
  name: "Taro",
  age: 20,
};

const updated = {
  ...user,
  name: "Jiro",
};
TypeScript

ここで updated の型は自動的に User になります。
スプレッド構文は「元のオブジェクトのプロパティを全部コピーして、一部を上書きした新しいオブジェクトを作る」イメージです。

このときも、型安全はちゃんと効いています。

const bad = {
  ...user,
  age: "21",  // エラー: Type 'string' is not assignable to type 'number'.
};
TypeScript

スプレッド構文を使うときのポイントは、「元のオブジェクトに型が付いていれば、その型を引き継いだまま更新できる」ということです。


更新用のヘルパー関数を作ると、もっと安全になる

部分更新を型で表現する

よくあるのが、「User の一部だけを更新したい」というケースです。
そのたびにスプレッドを書くのもいいですが、ヘルパー関数にすると意図がはっきりします。

type User = {
  name: string;
  age: number;
};

function updateUser(user: User, patch: Partial<User>): User {
  return { ...user, ...patch };
}
TypeScript

ここで Partial<User> は、「User のすべてのプロパティを optional にした型」です。
つまり、patch には name だけでも age だけでも、両方でも渡せます。

const user: User = { name: "Taro", age: 20 };

const u1 = updateUser(user, { name: "Jiro" });   // OK
const u2 = updateUser(user, { age: 21 });        // OK
const u3 = updateUser(user, { age: "21" });      // エラー
const u4 = updateUser(user, { nmae: "Ken" });    // エラー
TypeScript

ここで守られているのは二つです。

一つは、「User に存在するプロパティしか更新できない」こと。
もう一つは、「そのプロパティの型に合う値しか渡せない」こと。

「更新の窓口を関数にして、その引数に型を付ける」
これだけで、オブジェクト更新の安全性はかなり上がります。


readonly と「更新しない」ことを型で表現する

そもそも変えてはいけないものは、更新できないようにする

型安全な更新の一つの極端な形が、「更新させない」です。
ID や作成日時など、「変わるべきではない」プロパティは readonly にしておきます。

type User = {
  readonly id: number;
  name: string;
};

let user: User = {
  id: 1,
  name: "Taro",
};

user.name = "Jiro"; // OK
user.id = 2;        // エラー: Cannot assign to 'id' because it is a read-only property.
TypeScript

さらに、「この関数はオブジェクトを絶対に書き換えない」と宣言したいときは、Readonly<T> を使います。

function printUser(user: Readonly<User>) {
  console.log(user.name);
  // user.name = "X"; // エラー
}
TypeScript

「ここは変えていい」「ここは変えちゃダメ」を型で表現すること自体が、型安全な更新の一部です。


ネストしたオブジェクトの更新を型安全にやる

ネストしていても「型に沿って」更新する

少しだけ複雑な例にします。

type Address = {
  city: string;
  zip: string;
};

type User = {
  name: string;
  address: Address;
};

const user: User = {
  name: "Taro",
  address: {
    city: "Tokyo",
    zip: "100-0001",
  },
};
TypeScript

city だけ変えたいとき、よくある書き方はこうです。

const updated: User = {
  ...user,
  address: {
    ...user.address,
    city: "Osaka",
  },
};
TypeScript

ここでも、型がちゃんと効いています。

zip を number にしようとするとエラーになります。

const bad: User = {
  ...user,
  address: {
    ...user.address,
    zip: 12345, // エラー: Type 'number' is not assignable to type 'string'.
  },
};
TypeScript

ネストが深くなっても、「型を定義しておく → スプレッドで更新する → 代入先に型を付ける」
この流れを守れば、型安全は維持できます。


まとめ:型安全なオブジェクト更新の「考え方」

テクニックはいくつかありますが、根っこにある考え方はシンプルです。

オブジェクトの「形」と「プロパティの型」を、まずきちんと型として定義する。
その型を守るように、更新の仕方を設計する。
更新の入口(代入先の変数や更新関数の引数)に、必ずその型をぶら下げておく。

そうすると、TypeScript がこういうことを全部チェックしてくれます。

存在しないプロパティを更新していないか。
間違った型の値を入れていないか。
本来変わるべきでないものを変えようとしていないか。

「型に沿ってしか更新できない」状態を作ること。
それが、型安全なオブジェクト更新の本質です。

タイトルとURLをコピーしました