ES2023仕様では、元の配列を破壊(変更)する代わりに新しい配列を返す4つの配列メソッド——toSorted、toReversed、toSpliced、およびwith——が導入されました。Reactや、イミュータブル(不変)な状態に依存するその他のUIライブラリにおいて、これらのヘルパーメソッドは、長らくバグやボイラープレートの温床となっていたスプレッド演算子を用いたテクニックを置き換えることができます。
なぜこの変更が重要なのか
Reactは、前の状態の参照と新しい参照を比較することで、コンポーネントを再レンダリングするかどうかを決定します。参照が変わっていない場合、Reactは「何も変更されていない」と判断します。従来のArray.prototype.sortメソッドは、配列をその場で(in-place)ソートして同じ参照を返すため、setTasks(prev => prev.sort(fn))のような呼び出しを行うと、Reactが更新を検知できなくなります。その結果、UIが古いデータのまま更新されず、実際のプロジェクトで非常によく見られるバグを引き起こします。
開発者はこれまで、まず配列をクローンしてからソートを行うことでこの問題に対処してきました(通常はスプレッド演算子を使用します)。これにより、ソートのステップで新しい参照が生成されるようにします。
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));
このパターンは機能しますが、コードが冗長になり、書き忘れも起こりやすくなります。新しいES2023のメソッドは、元の配列に手を加えることなく、直接的かつ読みやすい方法で新しい配列を生成することを可能にします。
4つの非破壊的なメソッド
toSorted(compareFn?)–sortと同様に動作しますが、ソート済みのコピーを返します。元の配列に変更は加えられません。toReversed()–reverseに代わるものです。元の順序を維持したまま、反転させたコピーを返します。toSpliced(start, deleteCount, ...items)– 副作用なしでspliceと同じ動作をします。返される配列には挿入や削除が反映されますが、元の配列はそのままです。with(index, value)–indexにある要素をvalueに置き換えた新しい配列を返します。mapやスプレッド演算子を用いた要素の置き換えという一般的なパターンを代替します。
これら4つのメソッドはすべてECMAScript標準の一部であり、主要なブラウザの現行バージョンおよびNode.js 20で利用可能です。
コードの書き方の変化
リストのソート
// Before
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));
// After
setTasks(prev => prev.toSorted((a, b) => b.priority - a.priority));
単一アイテムの更新
// Before
setItems(prev =>
prev.map((item, i) => (i === idx ? newItem : item))
);
// After
setItems(prev => prev.with(idx, newItem));
配列の反転
setLogs(prev => prev.toReversed());
要素の削除
setTags(prev => prev.toSpliced(removeIdx, 1));
新しい構文により、余分なスプレッド演算子やmapによるループの必要性がなくなり、状態の更新がより読みやすく、エラーが起きにくくなります。
恩恵を受ける層と、導入を躊躇する可能性がある層
開発者:React、Vue、Redux、Zustand、あるいはイミュータブルなデータ構造を必要とするあらゆるフレームワークを使用している開発者は、より明確なメンタルモデルを得られます。「メソッドを呼び出し、新しい配列を受け取り、それをセッターに渡す」というシンプルな流れです。また、ボイラープレートが削減されることで、エンジンがソート前に中間コピーを作成する手間が省け、レンダリングサイクルを数ミリ秒短縮できる可能性もあります。
レガシーブラウザを使用するチーム:ポリフィルを含める必要があるかもしれません。これらのメソッドは古いバージョンのSafariやInternet Explorerには存在しないため、それらのプラットフォームをターゲットとするプロダクションビルドでは、フォールバックをバンドルする必要があります。これによりバンドルサイズがわずかに増加しますが、可読性の向上というメリットと比較すれば、多くの場合、そのトレードオフは十分に価値があります。
ライブラリ作者:新しいシグネチャを公開するために、型定義(TypeScriptなど)を更新する必要があるかもしれません。これらの定義が公式の@typesパッケージに反映されるまでは、開発者の環境で一時的な型エラーが発生する可能性があります。
今後の注目点
- 採用状況の指標 – ESLintなどのツールにおいて、状態セッター内での破壊的な配列操作をフラグ立てし、新しいメソッドの使用を促すルールがまもなく追加される可能性があります。
- パフォーマンス調査 – 初期のベンチマークでは、ネイティブの非破壊メソッドは「スプレッド演算子によるクローン+破壊的操作」よりも高速であることが示唆されていますが、実環境での影響については今後のデータが待たれます。
- さらなる提案 – ECMAScript委員会は、「デフォルトでイミュータブル」なAPIの探索を続けています。今後の提案段階を注視することで、同様のパターンに適合するさらなるヘルパーが登場するかもしれません。
まとめ
最新のECMAScript仕様は、UI開発者に対し、数え切れないほどのバグの原因となってきたスプレッド演算子による複雑な操作なしに、状態をイミュータブルに保つための組み込みの簡潔な手段を提供します。sort、reverse、splice、およびインデックスベースの置き換えを、toSorted、toReversed、toSpliced、およびwithに切り替えることで、Reactの変更検知を意図通りに機能させ、コードの可読性を高めることができます。ターゲットとするブラウザが新しいメソッドをサポートしている場合、あるいはポリフィルを使用する用意がある場合は、古いパターンを廃止し、言語本来の機能に処理を任せるべき時が来ています。
