t28.dev

Immer の immutableっぷりを表面上だけ見る

2022/4/17
Tech

Immer で変更を加えたオブジェクトがどのような状態になっているかを確認したくなったので、メモ。

Immutable なんだから変更対象のオブジェクトを直接変更せずに新しいオブジェクトを作って変更するんだよ!って話でもあるし、公式ドキュメントから参照されているブログ記事を読めば良い話でもある。

ref: Introducing Immer: Immutability the easy way

source

[edit with immer]

withNativeSpread/withImmer 👈 こいつがどんな感じか

何も変えない

const { produce } = require("immer");

const source = {
  primitive: "1",
  obj: { name: "1-1" },
  arr: [{ name: "1-[1]" }, { name: "1-[2]" }],
};

const withNativeSpread = { ...source };
const withImmer = produce(source, (draft) => {});

console.log(source === withNativeSpread); // 💡 false
console.log(source === withImmer); // true

console.log(source.primitive === withNativeSpread.primitive); // true
console.log(source.primitive === withImmer.primitive); // true

console.log(source.obj === withNativeSpread.obj); // true
console.log(source.obj === withImmer.obj); // true

console.log(source.obj.name === withNativeSpread.obj.name); // true
console.log(source.obj.name === withImmer.obj.name); // true

console.log(source.arr === withNativeSpread.arr); // true
console.log(source.arr === withImmer.arr); // true

console.log(source.arr[0] === withNativeSpread.arr[0]); // true
console.log(source.arr[0] === withImmer.arr[0]); // true

console.log(source.arr[0].name === withNativeSpread.arr[0].name); // true
console.log(source.arr[0].name === withImmer.arr[0].name); // true

console.log(source.arr[1] === withNativeSpread.arr[1]); // true
console.log(source.arr[1] === withImmer.arr[1]); // true

console.log(source.arr[1].name === withNativeSpread.arr[1].name); // true
console.log(source.arr[1].name === withImmer.arr[1].name); // true

プリミティブなプロパティを変更する

const { produce } = require("immer");

const source = {
  primitive: "1",
  obj: { name: "1-1" },
  arr: [{ name: "1-[1]" }, { name: "1-[2]" }],
};

const withNativeSpread = { ...source, primitive: "changed" };
const withImmer = produce(source, (draft) => {
  draft.primitive = "changed";
});

console.log(source === withNativeSpread); // 💡 false
console.log(source === withImmer); // 💡 false

console.log(source.primitive === withNativeSpread.primitive); // 💡 false
console.log(source.primitive === withImmer.primitive); // 💡 false

console.log(source.obj === withNativeSpread.obj); // true
console.log(source.obj === withImmer.obj); // true

console.log(source.obj.name === withNativeSpread.obj.name); // true
console.log(source.obj.name === withImmer.obj.name); // true

console.log(source.arr === withNativeSpread.arr); // true
console.log(source.arr === withImmer.arr); // true

console.log(source.arr[0] === withNativeSpread.arr[0]); // true
console.log(source.arr[0] === withImmer.arr[0]); // true

console.log(source.arr[0].name === withNativeSpread.arr[0].name); // true
console.log(source.arr[0].name === withImmer.arr[0].name); // true

console.log(source.arr[1] === withNativeSpread.arr[1]); // true
console.log(source.arr[1] === withImmer.arr[1]); // true

console.log(source.arr[1].name === withNativeSpread.arr[1].name); // true
console.log(source.arr[1].name === withImmer.arr[1].name); // true

ネストされたオブジェクト内のプリミティブなプロパティを変更する

const { produce } = require("immer");

const source = {
  primitive: "1",
  obj: { name: "1-1" },
  arr: [{ name: "1-[1]" }, { name: "1-[2]" }],
};

const withNativeSpread = { ...source, obj: { ...source.obj, name: "changed" } };
const withImmer = produce(source, (draft) => {
  draft.obj.name = "changed";
});

console.log(source === withNativeSpread); // 💡 false
console.log(source === withImmer); // 💡 false

console.log(source.primitive === withNativeSpread.primitive); // true
console.log(source.primitive === withImmer.primitive); // true

console.log(source.obj === withNativeSpread.obj); // 💡 false
console.log(source.obj === withImmer.obj); // 💡 false

console.log(source.obj.name === withNativeSpread.obj.name); // 💡 false
console.log(source.obj.name === withImmer.obj.name); // 💡 false

console.log(source.arr === withNativeSpread.arr); // true
console.log(source.arr === withImmer.arr); // true

console.log(source.arr[0] === withNativeSpread.arr[0]); // true
console.log(source.arr[0] === withImmer.arr[0]); // true

console.log(source.arr[0].name === withNativeSpread.arr[0].name); // true
console.log(source.arr[0].name === withImmer.arr[0].name); // true

console.log(source.arr[1] === withNativeSpread.arr[1]); // true
console.log(source.arr[1] === withImmer.arr[1]); // true

console.log(source.arr[1].name === withNativeSpread.arr[1].name); // true
console.log(source.arr[1].name === withImmer.arr[1].name); // true

ネストされた配列内のオブジェクトのプリミティブなプロパティを変更する

const { produce } = require("immer");

const source = {
  primitive: "1",
  obj: { name: "1-1" },
  arr: [{ name: "1-[1]" }, { name: "1-[2]" }],
};

const withNativeSpread = { ...source, arr: [...source.arr] };
withNativeSpread.arr[0] = {
  ...source.arr[0],
  name: "changed",
};
const withImmer = produce(source, (draft) => {
  draft.arr[0].name = "changed";
});

console.log(source === withNativeSpread); // 💡 false
console.log(source === withImmer); // 💡 false

console.log(source.primitive === withNativeSpread.primitive); // true
console.log(source.primitive === withImmer.primitive); // true

console.log(source.obj === withNativeSpread.obj); // true
console.log(source.obj === withImmer.obj); // true

console.log(source.obj.name === withNativeSpread.obj.name); // true
console.log(source.obj.name === withImmer.obj.name); // true

console.log(source.arr === withNativeSpread.arr); // 💡 false
console.log(source.arr === withImmer.arr); // 💡 false

console.log(source.arr[0] === withNativeSpread.arr[0]); // 💡 false
console.log(source.arr[0] === withImmer.arr[0]); // 💡 false

console.log(source.arr[0].name === withNativeSpread.arr[0].name); // 💡 false
console.log(source.arr[0].name === withImmer.arr[0].name); // 💡 false

console.log(source.arr[1] === withNativeSpread.arr[1]); // true
console.log(source.arr[1] === withImmer.arr[1]); // true

console.log(source.arr[1].name === withNativeSpread.arr[1].name); // true
console.log(source.arr[1].name === withImmer.arr[1].name); // true