Deep JavaScript
この書籍をサポートしてください: 購入 または 寄付
(広告、ブロックしないでください)

7 データの破壊的・非破壊的更新



この章では、データの更新方法を 2 つ学習します。

後者は、最初にコピーを作成してから破壊的に変更するのと似ていますが、同時に両方を行います。

7.1 例: オブジェクトの破壊的および非破壊的更新

次のコードは、オブジェクトのプロパティを破壊的に更新する関数と、オブジェクトに対して関数を使用する方法を示しています。

function setPropertyDestructively(obj, key, value) {
  obj[key] = value;
  return obj;
}

const obj = {city: 'Berlin', country: 'Germany'};
setPropertyDestructively(obj, 'city', 'Munich');
assert.deepEqual(obj, {city: 'Munich', country: 'Germany'});

次のコードは、オブジェクトの非破壊的更新を示しています。

function setPropertyNonDestructively(obj, key, value) {
  const updatedObj = {};
  for (const [k, v] of Object.entries(obj)) {
    updatedObj[k] = (k === key ? value : v);
  }
  return updatedObj;
}

const obj = {city: 'Berlin', country: 'Germany'};
const updatedObj = setPropertyNonDestructively(obj, 'city', 'Munich');

// We have created an updated object:
assert.deepEqual(updatedObj, {city: 'Munich', country: 'Germany'});

// But we didn’t change the original:
assert.deepEqual(obj, {city: 'Berlin', country: 'Germany'});

スプレッド演算子を使用すると、setPropertyNonDestructively() がより簡潔になります。

function setPropertyNonDestructively(obj, key, value) {
  return {...obj, [key]: value};
}

setPropertyNonDestructively() の両方のバージョンは 浅く 更新します。つまり、オブジェクトの最上位レベルのみを変更します。

7.2 例: 配列の破壊的および非破壊的更新

次のコードは、配列の要素を破壊的に更新する関数と、配列に対して関数を使用する方法を示しています。

function setElementDestructively(arr, index, value) {
  arr[index] = value;
}

const arr = ['a', 'b', 'c', 'd', 'e'];
setElementDestructively(arr, 2, 'x');
assert.deepEqual(arr, ['a', 'b', 'x', 'd', 'e']);

次のコードは、配列の非破壊的更新を示しています。

function setElementNonDestructively(arr, index, value) {
  const updatedArr = [];
  for (const [i, v] of arr.entries()) {
    updatedArr.push(i === index ? value : v);
  }
  return updatedArr;
}

const arr = ['a', 'b', 'c', 'd', 'e'];
const updatedArr = setElementNonDestructively(arr, 2, 'x');
assert.deepEqual(updatedArr, ['a', 'b', 'x', 'd', 'e']);
assert.deepEqual(arr, ['a', 'b', 'c', 'd', 'e']);

.slice() とスプレッド演算子を使用すると、setElementNonDestructively() がより簡潔になります。

function setElementNonDestructively(arr, index, value) {
  return [
    ...arr.slice(0, index), value, ...arr.slice(index+1)];
}

setElementNonDestructively() の両方のバージョンは 浅く 更新します。つまり、配列の最上位レベルのみを変更します。

7.3 手動によるディープ更新

これまでのところ、データを浅くしか更新していません。ディープ更新に取り組みましょう。次のコードは、手動でそれを行う方法を示しています。name と employer を変更しています。

const original = {name: 'Jane', work: {employer: 'Acme'}};
const updatedOriginal = {
  ...original,
  name: 'John',
  work: {
    ...original.work,
    employer: 'Spectre'
  },
};

assert.deepEqual(
  original, {name: 'Jane', work: {employer: 'Acme'}});
assert.deepEqual(
  updatedOriginal, {name: 'John', work: {employer: 'Spectre'}});

7.4一般的なディープ更新の実装

次の関数は、一般的なディープ更新を実装します。

function deepUpdate(original, keys, value) {
  if (keys.length === 0) {
    return value;
  }
  const currentKey = keys[0];
  if (Array.isArray(original)) {
    return original.map(
      (v, index) => index === currentKey
        ? deepUpdate(v, keys.slice(1), value) // (A)
        : v); // (B)
  } else if (typeof original === 'object' && original !== null) {
    return Object.fromEntries(
      Object.entries(original).map(
        (keyValuePair) => {
          const [k,v] = keyValuePair;
          if (k === currentKey) {
            return [k, deepUpdate(v, keys.slice(1), value)]; // (C)
          } else {
            return keyValuePair; // (D)
          }
        }));
  } else {
    // Primitive value
    return original;
  }
}

value を更新するツリーのルートと見なした場合、deepUpdate() は単一のブランチ (A 行と C 行) のみをディープに変更します。他のすべてのブランチは浅くコピーされます (B 行と D 行)。

deepUpdate() の使用方法は次のとおりです。

const original = {name: 'Jane', work: {employer: 'Acme'}};

const copy = deepUpdate(original, ['work', 'employer'], 'Spectre');
assert.deepEqual(copy, {name: 'Jane', work: {employer: 'Spectre'}});
assert.deepEqual(original, {name: 'Jane', work: {employer: 'Acme'}});