データの不変性の保持 | イミュータビリティ | JavaScript 超完全入門 基本から発展までのすべて

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

データの不変性とは?

不変性(イミュータビリティ、Immutability)とは、一度作成したデータを直接変更しないという考え方です。

JavaScriptでは、オブジェクトや配列は基本的に変更可能です。そのため、不変性を保ちたい場合は、元のデータを直接書き換えるのではなく、変更後の新しいデータを作成するようにします。

この考え方は、関数型プログラミングだけでなく、Reactなどのフロントエンド開発でも非常に重要です。

データの不変性を保つ理由

不変性を保つと、データの変更箇所が分かりやすくなり、予期しないバグを防ぎやすくなります。

利点 説明
副作用を防ぎやすい 元のデータを変更しないため、他の処理へ意図しない影響を与えにくくなります。
デバッグしやすい いつ新しいデータが作られたのかを追いやすくなります。
状態管理と相性が良い Reactなどでは、データの変更を検知しやすくなります。
安全に再利用できる 元のデータを別の場所でも安心して使えます。

データの不変性を保つ方法

JavaScriptで不変性を保つ基本は、次の考え方です。

  • 元のオブジェクトを直接変更しない。
  • 元の配列を直接変更しない。
  • 変更後の新しいオブジェクトや配列を作成する。

特に、push()splice()、プロパティへの直接代入などは元のデータを変更するため注意が必要です。

不変なオブジェクトの作成方法

オブジェクトの値を変更したい場合は、元のオブジェクトを直接変更せず、新しいオブジェクトを作成します。

Object.assign()を使う例

const originalPerson = {
    name: "Alice",
    age: 25
};

const updatedPerson = Object.assign({}, originalPerson, {
    age: 26
});

console.log(originalPerson);
console.log(updatedPerson);

// { name: "Alice", age: 25 }
// { name: "Alice", age: 26 }

この例では、originalPersonを直接変更せず、updatedPersonという新しいオブジェクトを作成しています。

スプレッド構文を使った不変性の保持

現在のJavaScriptでは、オブジェクトのコピーにはスプレッド構文を使うことが多くなっています。

const originalPerson = {
    name: "Alice",
    age: 25
};

const updatedPerson = {
    ...originalPerson,
    age: 27
};

console.log(originalPerson);
console.log(updatedPerson);

// { name: "Alice", age: 25 }
// { name: "Alice", age: 27 }

...originalPersonで元のオブジェクトを展開し、ageだけを新しい値で上書きしています。

読みやすいため、実務でもよく使われる書き方です。

不変な配列の作成方法

配列でも、元の配列を直接変更せずに新しい配列を作成します。

要素を追加する例

const originalArray = [1, 2, 3];

const newArray = [...originalArray, 4];

console.log(originalArray);
console.log(newArray);

// [1, 2, 3]
// [1, 2, 3, 4]

push()を使うと元の配列が変更されますが、スプレッド構文を使えば新しい配列を作成できます。

要素を削除する例

const numbers = [1, 2, 3, 4];

const filteredNumbers = numbers.filter((number) => number !== 3);

console.log(numbers);
console.log(filteredNumbers);

// [1, 2, 3, 4]
// [1, 2, 4]

filter()は新しい配列を返すため、不変性を保ちながら要素を削除できます。

要素を更新する例

const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" }
];

const updatedUsers = users.map((user) => {
    if (user.id === 2) {
        return {
            ...user,
            name: "Charlie"
        };
    }

    return user;
});

console.log(updatedUsers);

// [
//   { id: 1, name: "Alice" },
//   { id: 2, name: "Charlie" }
// ]

map()を使うと、配列の各要素を確認しながら新しい配列を作成できます。

不変性を保つためのツール

JavaScriptでは、ライブラリを使って不変性を扱いやすくすることもできます。

代表的なものにImmerがあります。

Immerの考え方

Immerを使うと、コード上は直接変更しているように見えても、実際には新しいデータを作成できます。

import { produce } from "immer";

const originalState = {
    name: "Alice",
    age: 25
};

const nextState = produce(originalState, (draft) => {
    draft.age = 26;
});

console.log(originalState);
console.log(nextState);

// { name: "Alice", age: 25 }
// { name: "Alice", age: 26 }

Reactの状態管理や複雑なオブジェクト更新では、Immerのようなライブラリが役立つことがあります。

浅いコピーに注意する

スプレッド構文やObject.assign()は便利ですが、基本的には浅いコピーです。

つまり、ネストしたオブジェクトまでは完全にコピーされません。

const original = {
    name: "Alice",
    address: {
        city: "Tokyo"
    }
};

const copied = {
    ...original
};

copied.address.city = "Osaka";

console.log(original.address.city);

// Osaka

この例では、addressオブジェクトは共有されているため、コピー側を変更すると元のデータにも影響します。

ネストが深いデータを扱う場合は、必要な階層ごとにコピーするか、structuredClone()やImmerなどの利用を検討します。

初心者が知っておきたいポイント

  • 不変性とは、元のデータを直接変更しない考え方である。
  • オブジェクトや配列を更新するときは、新しいデータを作成する。
  • push()splice()は元の配列を変更するため注意が必要である。
  • map()filter()、スプレッド構文は不変性を保ちやすい。
  • スプレッド構文は浅いコピーである点に注意する。

まとめ

  • データの不変性とは、元のデータを直接変更しない性質のことである。
  • 不変性を保つと、副作用を防ぎ、デバッグしやすいコードになる。
  • オブジェクトの更新にはスプレッド構文やObject.assign()を利用できる。
  • 配列の更新にはスプレッド構文、map()filter()などが役立つ。
  • ネストしたデータでは浅いコピーに注意し、必要に応じてImmerなどのライブラリを活用する。