useStateで配列を使っていく
初期値の内容に応じて配列を作る。空配列でもOK!!
export default function Home() {
const [array, setArray] = useState([1,2,3]);
return (
<>
<button onClick={handleAdd}>追加</button>
<ul>
{array.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</>
)
}
ちなみに上記のコードでは[1]・[2]・[3]の三つが配列に加わり、<li>
が三つ追加される。
export default function Home() {
const [array, setArray] = useState([]);
const handleAdd = useCallback(() => {
setArray((array) => {
const newArray = [...array,1]
return newArray;
});
},[])
return (
<>
<button onClick={handleAdd}>追加</button>
<ul>
{array.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</>
)
}
上記のコードでは「追加」を押したら、配列に「1」が追加されていく。
setArray((array) => {
const newArray = [...array,1]
return newArray;
});
ここの部分を解説。
setArrayの中が関数になっているので、前回の配列情報を使える。
「追加」がクリックされるとその時の配列を認識(arrayのこと)し、「1」を追加した新しい配列をnewArray
に格納する。(破壊的であるpushを非破壊的に記述している。)
そして仕上げにnewArray
をreturn
で返す。
※key(1)の被りでエラーが出ると思うが、実際の実装ではデータベースのID等を使うと思うので一旦無視
入力したテキストを配列に格納していきたい
import { useCallback, useEffect, useState } from 'react';
export default function Home() {
const [text , setText] = useState("");
const [array, setArray] = useState([1,2,3]);
{/* ここのtextに注目 */}
const handleAdd = useCallback(() => {
setArray((array) => {
const newArray = [...array,text]
return newArray;
});
},[text])
return (
<>
<input type="text" value={text} onChange={e => {
setText(e.target.value);
}} />
<button onClick={handleAdd}>追加</button>
<ul>
{array.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</>
)
}
handleAdd
関数部分の解説。
text
はuseStateを使ったインプットタグの入力値。
引数のarrayで前回の配列を受け取り、その配列にtextを追加する。変更点は追加されたtextだけなので、新たにtextだけが追加される。
注意点は第二引数に[text]を書き忘れないこと!
何故ならコールバック関数を使っているため、textを変更したところでhandleAddは再レンダリングされない。一番初めに[1,2,3]を取得し、そのままになってしまう。
ちなみにsome(配列の中の少なくとも1つの要素が当てはまるかどうかを判定し、trueかfalseを返す)を使うと、二重登録を防ぐ事もできる!
return array
を記述しないとその下の処理が実行されて、結局追加されてしまうので忘れないように!
const handleAdd = useCallback(() => {
setArray((array) => {
if (array.some((item) => item === text)) {
alert("既に存在しています");
return array;
}
const newArray = [...array,text]
return newArray;
});
},[text])
(非)破壊的メソッドとは
破壊的メソッドとは、元のデータ(今回だと配列)が変更されることをいう。
そのため変更前の配列を使用するために変数を指定しても、思っている結果と違うことがある。
それに比べて非破壊的メソッドとは、新しい配列を用意して作業することで、元のデータを保持することができる。
そこで役に立つのがスプレッド構文なのだが、改めて復習したいと思う。
あらためてスプレッド構文のおさらい
const arr = ['一つ目', '二つ目'];
console.log(arr);//['一つ目', '二つ目']
console.log(...arr);//一つ目 二つ目
arrをそのまま出力すると配列をそのまま出力され、...arr(スプレッド構文)を出力すると配列の中身が個別で出力される。
それを応用すると、要素を簡単に引数に入れることができる
onst arr = [1, 2, 3, 4, 5];
const newArr = (num1, num2, num3, num4, num5) => {
return num1 + num2 + num3 + num4 + num5;
};
console.log(newArr(arr[0], arr[1], arr[2], arr[3], arr[4]));//15
console.log(newArr(...arr));//15
同じ15が返るのにconsole.log(newArr(...arr))
の方が圧倒的に楽!
そしてここからが重要
配列をコピーしたい!!!
//下記の配列を変数arr5に代入したい!
const arr = [1, 2, 3, 4, 5];
const arr5 = arr;
console.log(arr5);//[1, 2, 3, 4, 5]
無事arr5の中身が[1, 2, 3, 4, 5]になったのでめでたしめでたし!!ではない。
その理由が下記コード。
arr5[0] = 800;
console.log(arr);//[800, 2, 3, 4, 5]
console.log(arr5);//[800, 2, 3, 4, 5]
あれ?arr5しか変更してないのに、arrまで変わってしまっている、、、
(てかReactって状態が変更されないと再レンダリングされないじゃん?元の配列も一緒に変わったんなら、一生状態に変更がないから再レンダリングされないよね・・・。おもしろ・・・。
元の状態と差異を出すためにも、非破壊的メソッドを使って再レンダリングさせるのか。)
そうつまり配列のコピーは「ニ○・ロビン」とようなものなのだ。
たくさん分身は出せるけど、そのダメージは何故か自分にかえってくる。
ということでニ○・ロビン = 非破壊的メソッドという式が成り立つ。
スプレッド構文を使って非破壊的メソッドにする
const arr = [1, 2, 3, 4, 5];
//ここを少しかえる
const arr5 = [...arr];
arr5[0] = 800;
console.log(arr);//[1, 2, 3, 4, 5]
console.log(arr5);//[800, 2, 3, 4, 5]
よし!これで本当にめでたしめでたし!!
何故こうなったのかというと、const arr5 = [...arr]
このように代入することでコピーではなく新しい配列を作ることができるからだ。
つまりニ○・ロビンのように見えてニ○・ロビンではないということだ。
なぜ破壊的メソッドがダメなのか
これを理解するためには「ミュータブル(変更可能)・イミュータブル(変更不可能)」について知る必要がある。
簡単にいうと一度値を作成した後に、その値を変更できるかどうかということ。
文字列や数値はデフォルトでイミュータブルになっている。
要は変数に何はアクションを与えたときに、参照元の変数(元の変数)に影響を与えないということ。
let str = "Hello";
str.concat(" World"); // 新しい文字列 "Hello World" が生成されるが、str の値は変わらない
let num = 10;
num + 5; // 新しい数値 15 が生成されるが、num の値は変わらない
それに比べて配列とオブジェクトはデフォルトでミュータブルになっている。
(参考コードは「あらためてスプレッド構文のおさらい」にあるよ)
そのため配列とオブジェクトもイミュータブルとして扱うべく、破壊的メソッドを避けるような記述をするようになった。