3秒でわかる
作った後に中身を変えられないデータの扱い方。書き換える代わりに新しい値を作ることで、意図しない共有と予期せぬ変化を防ぐために使います。
もう少し詳しく
どういうものか
イミュータブルは「作った後で中身が変わらない」性質を指します。Python の文字列やタプル、Java の String、JavaScript のプリミティブ値がこれにあたります。値を変えたいときは、元を書き換えるのではなく、変更を反映した新しい値を作って差し替えます。反対にリストや辞書、配列のように後から中身を変えられるものはミュータブルと呼びます。
なぜ必要か
ミュータブルな値は、複数の場所から同じ実体を指せてしまいます。関数に配列を渡したら、呼ばれた側で並び替えられて、呼んだ側の配列まで順序が変わっていた、という事故はここから起きます。原因が離れた場所にあるため、追跡には時間がかかります。値を書き換えない約束にしておけば、変数が指す中身は自分が代入した時のままだと信じられるので、読むときに追う範囲が狭くなります。
React が状態を更新するときに元の配列を並び替えず新しい配列を返すよう求めるのも、前回と今回のオブジェクトが同一かどうかで再描画の要否を判断しているためです。中身だけ書き換えると、同じオブジェクトのままなので変化に気づけません。
具体例
const todos = [{ id: 1, done: false }, { id: 2, done: false }];
// 書き換えてしまう例(元の配列が変わる)
todos[0].done = true;
// 新しい値を作る例
const next = todos.map((t) =>
t.id === 1 ? { ...t, done: true } : t
);
// 並び替えも、コピーしてから行う
const sorted = [...todos].sort((a, b) => a.id - b.id);つまずきやすいところ
Python で危ないのは、既定の引数にリストを書いてしまう書き方です。既定値は関数定義のときに一度だけ作られるため、呼び出しをまたいで中身が積み上がります。
def add(item, bucket=[]): # 危ない
bucket.append(item)
return bucket
def add_safe(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucketもう一つは浅いコピーの誤解です。スプレッド構文や list() で複製できるのは一段目までで、入れ子になったオブジェクトは元と共有されたままです。深い階層まで独立させたいときは、必要な階層まで明示的に作り直します。
覚え方
「直さず、作り直す」と唱えるだけで、書き換えの誘惑をだいたい避けられます。