Back

非破壊的配列:より安全なJavaScriptコードの書き方

非破壊的配列:より安全なJavaScriptコードの書き方

JavaScriptで配列を変更する際、コードの他の部分が依存しているデータを誤って変更してしまう可能性があります。これにより、追跡が困難なバグが発生します。解決策は何でしょうか?元の配列を変更する代わりに新しい配列を返す非破壊的配列メソッドを使用することです。

この記事では、JavaScriptの重要な非破壊的配列メソッド、より安全なコードを書くためになぜそれらが重要なのか、そしてプロジェクトで効果的に使用する方法について説明します。

重要なポイント

  • 非破壊的配列メソッドは元のデータを変更せずに新しい配列を返す
  • イミュータブルな操作を使用することで予期しない副作用を防ぎ、コードをより予測可能にする
  • map()filter()reduce()などのメソッドは破壊的操作のより安全な代替手段
  • スプレッド演算子は一般的な配列操作に対してクリーンな構文を提供する

JavaScriptにおいてイミュータビリティが重要な理由

配列を変更することは、アプリケーションで予期しない動作を引き起こす可能性があります。配列を関数に渡したり、コンポーネント間で共有したりする際、一箇所での変更がその配列への全ての参照に影響を与えます。

const originalTasks = ['Write code', 'Review PR', 'Deploy'];
const completedTasks = originalTasks;
completedTasks.push('Write tests');

console.log(originalTasks); // ['Write code', 'Review PR', 'Deploy', 'Write tests']
// 元の配列が変更されました!

これは特に、状態の変更がコンポーネントの再レンダリングを妨げるReactアプリケーションや、状態がイミュータブルでなければならないReduxにおいて問題となります。

破壊的メソッド vs 非破壊的メソッド:主な違い

破壊的メソッド(これらは避ける)

  • push()pop()shift()unshift() - 要素の追加または削除
  • sort() - 配列をその場でソート
  • reverse() - 配列の順序を逆転
  • splice() - 任意の位置で要素を追加/削除
  • fill() - 配列を値で埋める

非破壊的メソッド(これらを使用する)

  • map() - 各要素を変換
  • filter() - 条件に一致する要素を保持
  • reduce() - 要素を単一の値に結合
  • slice() - 配列の一部を抽出
  • concat() - 配列を結合

重要な非破壊的配列メソッド

map():変更なしの変換

配列を変更するforループを使用する代わりに、map()は変換された値で新しい配列を作成します:

const prices = [10, 20, 30];
const discountedPrices = prices.map(price => price * 0.8);

console.log(prices);          // [10, 20, 30] - 変更されていない
console.log(discountedPrices); // [8, 16, 24]

filter():安全な配列フィルタリング

元の配列に触れることなく要素を削除:

const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
  { name: 'Charlie', active: true }
];

const activeUsers = users.filter(user => user.active);
console.log(users.length);       // 3 - 元の配列は変更されていない
console.log(activeUsers.length); // 2

reduce():副作用なしの結合

外部変数を使用せずに配列から値を計算:

const orders = [
  { product: 'Laptop', price: 1200 },
  { product: 'Mouse', price: 25 }
];

const total = orders.reduce((sum, order) => sum + order.price, 0);
// ordersを変更せずに1225を返す

slice():配列の部分抽出

splice()を使用せずに配列のサブセットを取得:

const tasks = ['Task 1', 'Task 2', 'Task 3', 'Task 4'];
const firstTwo = tasks.slice(0, 2);
const lastTwo = tasks.slice(-2);

console.log(firstTwo); // ['Task 1', 'Task 2']
console.log(lastTwo);  // ['Task 3', 'Task 4']
console.log(tasks);    // 元の配列は変更されていない

concat():安全な配列結合

push()を使用せずに配列をマージ:

const completed = ['Task 1', 'Task 2'];
const pending = ['Task 3', 'Task 4'];
const allTasks = completed.concat(pending);

// またはスプレッド演算子を使用
const allTasksSpread = [...completed, ...pending];

JavaScript非破壊的配列のベストプラクティス

1. 破壊的操作を置き換える

// ❌ 避ける:pushによる変更
const items = [1, 2, 3];
items.push(4);

// ✅ より良い:新しい配列を作成
const newItems = [...items, 4];

2. 複雑な操作にはメソッドチェーンを使用

const products = [
  { name: 'Laptop', price: 1200, inStock: true },
  { name: 'Phone', price: 800, inStock: false },
  { name: 'Tablet', price: 600, inStock: true }
];

const affordableInStock = products
  .filter(p => p.inStock)
  .filter(p => p.price < 1000)
  .map(p => p.name);
// productsを変更せずに['Tablet']を返す

3. 単純な操作にはスプレッド演算子を使用

// インデックス位置の項目を削除
const removeAt = (arr, index) => [
  ...arr.slice(0, index),
  ...arr.slice(index + 1)
];

// インデックス位置の項目を更新
const updateAt = (arr, index, value) => [
  ...arr.slice(0, index),
  value,
  ...arr.slice(index + 1)
];

Reactにおけるより安全な状態管理

非破壊的メソッドはReactの状態更新に不可欠です:

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false }
  ]);

  const toggleTodo = (id) => {
    // ✅ 更新されたオブジェクトで新しい配列を作成
    setTodos(todos.map(todo =>
      todo.id === id 
        ? { ...todo, done: !todo.done }
        : todo
    ));
  };

  const removeTodo = (id) => {
    // ✅ 変更なしでtodoをフィルタリング
    setTodos(todos.filter(todo => todo.id !== id));
  };
}

パフォーマンスに関する考慮事項

非破壊的メソッドは新しい配列を作成しますが、現代のJavaScriptエンジンはこれらの操作を効率的に最適化します。予測可能でバグのないコードの利点は、通常、わずかなパフォーマンスの違いを上回ります。大きなデータセットを扱うパフォーマンス重視のコードでは、Immutable.jsImmerなどの専用ライブラリの使用を検討してください。

まとめ

非破壊的配列メソッドは、JavaScriptコードをより予測可能で、デバッグしやすくします。破壊的な対応メソッドの代わりにmap()filter()reduce()slice()concat()を使用することで、バグにつながる副作用を回避できます。このアプローチは、特にReactアプリケーションや関数型プログラミング原則に従う際に価値があります。今日からコード内の破壊的操作を置き換えることを始めましょう。将来のあなたがきっと感謝するでしょう。

よくある質問

はい、ただし各コンテキスト内で一貫性を保ってください。共有データ、状態管理、関数型プログラミングには非破壊的メソッドを使用してください。破壊的メソッドは、他の場所で参照されないローカルな一時配列に対しては使用可能です。

ほとんどのアプリケーションでは、パフォーマンスの違いは無視できる程度です。現代のJavaScriptエンジンはこれらの操作を効率的に最適化します。プロファイリングでボトルネックが確認された場合にのみ、非常に大きなデータセットやパフォーマンス重視のループに対して代替手段を検討してください。

スプレッド演算子またはsliceを使用して最初にコピーを作成し、そのコピーをソートします。例:const sorted = [...array].sort() または const sorted = array.slice().sort()。これにより元の配列の順序が保持されます。

sliceは非破壊的で、元の配列を変更せずに抽出された要素を含む新しい配列を返します。spliceは破壊的で、要素を削除または置換することで元の配列を直接変更し、削除された要素を返します。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay