スプレッドの復習
中身をばらまく 3 つの点
スプレッド構文は、配列やオブジェクトの中身を 1 つずつ取り出して、別の場所に並べ直す書き方です。書き方は点を 3 つ並べるだけです。
const titles = ["レシピ検索アプリ", "買い物メモツール"];
const nextTitles = [...titles, "写真ギャラリー"];
nextTitles; // ["レシピ検索アプリ", "買い物メモツール", "写真ギャラリー"]
titles; // ["レシピ検索アプリ", "買い物メモツール"] もとの配列はそのまま大事なのは最後の行です。titles は 2 件のまま変わっていません。[...titles, "写真ギャラリー"] は「もとの配列を書き換える」のではなく「もとの中身を並べた新しい配列を作る」からです。
先頭に足したいなら順番を入れ替えるだけです。
const nextTitles = ["写真ギャラリー", ...titles];オブジェクトでも同じことができる
オブジェクトでもスプレッドが使えます。同じプロパティ名が 2 回出てきたときは、あとに書いたほうが勝ちます。
const work = { id: 1, title: "レシピ検索アプリ", published: false };
const publishedWork = { ...work, published: true };
publishedWork; // { id: 1, title: "レシピ検索アプリ", published: true }
work; // { id: 1, title: "レシピ検索アプリ", published: false }「1 か所だけ変えた新しいオブジェクトを作る」ときの定番の形です。{ ...work, published: true } は、まず work の中身を全部並べ、そのあとに published を上書きしています。
なぜ React でこれが要るのか
React の state は、書き換えるのではなく作り直すものだからです。
第4章で出てくる useState は、値が別のものに入れ替わったときにだけ画面を描き直します。ところが push や代入で中身を直接いじると、配列やオブジェクトそのものは同じ入れ物のままです。React から見ると「何も変わっていない」ので、画面が更新されません。
// 画面が変わらない書き方
works.push(newWork);
setWorks(works);
// 新しい配列を渡すので画面が変わる
setWorks([...works, newWork]);オブジェクトの state も同じです。
setProfile({ ...profile, title: "フロントエンド担当" });この「もとを触らずに、新しいものを作って渡す」やり方を不変更新と呼びます。React を書いているあいだ、state を更新するときはほぼ毎回この形になります。第4章の作品の追加と削除で、そのまま回収します。
つまずきどころ
スプレッドがコピーするのは一番外側だけです。中に入れ子のオブジェクトがあると、そこは同じものを共有したままになります。
const profile = { name: "あおい", links: { github: "aoi" } };
const copy = { ...profile };
copy.links.github = "aoi-dev";
profile.links.github; // aoi-dev もとも変わってしまった入れ子を変えたいときは、内側にもスプレッドを書きます。今はこの落とし穴があることだけ覚えておけば十分です。
演習
作品リストに 1 件足す関数を書きます。もとの配列を書き換えず、新しい配列を返すのがポイントです。
要件
[...works, ...]の形で、もとの配列を書き換えずに新しい配列を返す- 追加する作品は
{ ...newWork, ... }の形で作り、id と published を足す - id は追加後の件数と同じ番号にする。3 件のリストに足すなら 4 になる
- published は false にする
入出力例
addWork([], {"category":"web","title":"レシピ検索アプリ"}) → [{"category":"web","id":1,"published":false,"title":"レシピ検索アプリ"}]
addWork([{"category":"web","id":1,"published":true,"title":"レシピ検索アプリ"}], {"category":"tool","title":"買い物メモツール"}) → [{"category":"web","id":1,"published":true,"title":"レシピ検索アプリ"},{"category":"tool","id":2,"published":false,"title":"買い物メモツール"}]
addWork([{"category":"web","id":1,"published":true,"title":"レシピ検索アプリ"},{"category":"tool","id":2,"published":true,"title":"買い物メモツール"},{"category":"web","id":3,"published":false,"title":"写真ギャラリー"}], {"category":"tool","title":"読書メモアプリ"}) → [{"category":"web","id":1,"published":true,"title":"レシピ検索アプリ"},{"category":"tool","id":2,"published":true,"title":"買い物メモツール"},{"category":"web","id":3,"published":false,"title":"写真ギャラリー"},{"category":"tool","id":4,"published":false,"title":"読書メモアプリ"}]
addWork([], {"category":"tool","title":"買い物メモツール"}) → [{"category":"tool","id":1,"published":false,"title":"買い物メモツール"}]