データの不変性とは?

不変性(イミュータビリティ、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などのライブラリを活用する。