React: splice() とイミュータビリティ(不変性)
概要
JavaScriptの splice() は配列から要素を削除・置換・追加できるメソッド
しかし、Reactのstate(状態)に対して直接使うと、バグの原因になる
1. splice() とは?
- 機能: 配列の既存の要素を変更(削除、置換、追加)する
-
構文:
array.splice(index, deleteCount, item1, ..., itemX) - 最大の特徴: 呼び出した元の配列そのものを変更してしまう
// サンプルコード
const fruits = ['apple', 'banana', 'orange'];
fruits.splice(1, 1, 'grape'); // 1番目から1個削除し、'grape'を追加
console.log(fruits); // 結果: ['apple', 'grape', 'orange']
// 元の 'fruits' 配列自体が変わる
2. React (State) で splice() を直接使うとなぜダメなのか?
Reactは、stateが変更されたことを検知して、
コンポーネントを再レンダリング(再描画)する
この「変更検知」は、基本的にstateの参照が変わったかどうかで判断される
splice() は元の配列の参照を変えずに、中身だけを変更してしまう
// Reactコンポーネント内(悪い例)
const [items, setItems] = useState(['a', 'b', 'c']);
const handleRemoveItem = (index) => {
// 悪い例:stateの配列を直接変更している
items.splice(index, 1);
setItems(items); // 参照が変わっていないので、Reactは変更を検知できない
};
// この結果、画面が再レンダリングされず、削除が反映されない(予期せぬ挙動をする)
結論: Reactにおいて、state(配列やオブジェクト)は
イミュータブル(不変)に扱うのが鉄則。stateを直接変更してはいけない
3. Reactでの正しい対処法(イミュータブルな操作)
元のstate配列を変更せず、新しい配列を作って setItems に渡す
A. 要素の削除: filter() を使用
filter() は、元の配列を変更せず、条件に合う要素だけで新しい配列を返す
// 良い例:filter を使用
const handleRemoveItem = (targetIndex) => {
const newItems = items.filter((item, index) => {
return index !== targetIndex;
});
setItems(newItems);
};
B. 要素の追加: スプレッド構文 (...) を使用
// 良い例:スプレッド構文で追加
const handleAddItem = (newItem) => {
const newItems = [...items, newItem]; // 末尾に追加
// const newItems = [newItem, ...items]; // 先頭に追加
setItems(newItems);
};
C. 要素の置換(更新): map() を使用
map() も、元の配列を変更せず、新しい配列を返す
// 良い例:map で特定要素を更新
const handleUpdateItem = (targetIndex, updatedItem) => {
const newItems = items.map((item, index) => {
return index === targetIndex ? updatedItem : item;
});
setItems(newItems);
};
D. どうしても splice() を使いたい場合
必ずコピーしてから使用
ただし、filter や map を使う方がReact的で推奨される
// 非推奨ではないが、冗長
const handleRemoveItem = (targetIndex) => {
const newItems = [...items]; // まず配列をコピーする
newItems.splice(targetIndex, 1); // コピーした配列を変更
setItems(newItems); // 新しい配列をセット
};