useState関係の公式ドキュメントを読む
useState関連のドキュメント読んだ時の個人的なメモです。
useStateのドキュメント
リファレンス
- useStateはフックであるため、コンポーネントのトップレベルまたはカスタムフック内でしか呼び出せない。ループや条件分の中などでは行えない。あとはreturnの後とか?
- strict modeではuseStateが2回呼び出される。これは開発モードだけの挙動であり本番ビルドには影響しない。
- 理由は純粋ではない関数を見つけるため。
- 純粋ではない関数ってなに?
- 複数回呼び出されても常に同じ値を返す関数のこと。
- コンポーネントは自分の仕事に集中するべき。レンダー前に存在していたオブジェクトや変数を書き換えないようにする。
- 代わりにstateをsetすること。
- 何かを変更する際は、イベントハンドラ内で行うこと!useEffectは最終手段。
-
push
,pop
,reverse
,sort
などは元の配列を書き換えてしまうので注意が必要
- Reactの思想では、すべてのコンポーネントは純粋関数であるべき。
- 純粋ではない関数ってなに?
- 理由は純粋ではない関数を見つけるため。
-
set関数
は次のレンダーのためのstate変数のみを更新する。そのため、setした後にすぐにstateを読み取っても更新はされていない。 - setした後のstateの値が変わらない場合は、reactはレンダリングをスキップする。
- 値の比較はObject.is()によって行われる。
- Object.isは
===
と判定が微妙に異なる。
- Object.isは
- 値の比較はObject.is()によって行われる。
- reactのsetはバッチ的に処理される。そのため、stateを更新するたびにレンダリングされるわけではない。
使用方法
- 更新用関数を渡さないと直前の値を元にstateの値を変更することができない。
- 更新用関数とは、
a => a + 1
のような関数
- 更新用関数とは、
[count, setCount] = useState(0)
// 3回呼び出しているが、値は1しか増えない。
handleChange = (num) => {
setCount(num + 1);
setCount(num + 1);
setCount(num + 1);
};
// 元の値から3増える。
handleChange = (num) => {
setCount(num => num + 1);
setCount(num => num + 1);
setCount(num => num + 1);
}
State内のオブジェクトの更新
ミューテーションとは?
- イミュータブル(不変)とは
- 値が変わることがなく「読み取り専用」なもの。真偽値や数値、文字列など。
[num, setNum] = useState(0)
// numの値は5になったが、初期値の0という数字そのものが5に変化したわけではない。置き換えただけ。
setNum(5)
- ミュータブル(書き換え可能)なもの
- オブジェクトは書き換えることができる。
const [position, setPosition] = useState({ x: 0, y: 0 });
// {x: 0, y: 0}というオブジェクトを{x: 5, y: 0}に書き換えている。
position.x = 5
state を読み取り専用として扱う
- 技術的には可能だが、Reactではオブジェクトもイミュータブルなものとして扱うべき。
- set関数を使わないと、Reactがオブジェクトを変更したことがわからないため。
- 解決策としてはset関数に新しいオブジェクトとして渡してあげる。
- set関数を使わないと、Reactがオブジェクトを変更したことがわからないため。
// BAD: Reactがオブジェクトが変更されたことを検知できない。
onPointerMove={e => {
position.x = e.clientX;
position.y = e.clientY;
}}
// GOOD: 新しいオブジェクトとしてset関数に渡しているのでレンダリングされる。
onPointerMove={e => {
setPosition({
x: e.clientX,
y: e.clientY
});
}}
スプレッド構文によるオブジェクトのコピー
- オブジェクトの値を変更したいときは、set関数に新しいオブジェクトとして渡すことが必要。
- 一つのプロパティだけ変更したいのに全部書くのは面倒。
- スプレッド構文 - JavaScript | MDNを使用することで全部書かなくて良くなる。
- 一つのプロパティだけ変更したいのに全部書くのは面倒。
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
// BAD: めんどくさい
setPerson({
firstName: e.target.value, // ここだけ変更したい
lastName: person.lastName,
email: person.email
});
// GOOD: オブジェクトスプレッド構文でオブジェクトをコピーする。
setPerson({
...person, // コピー
firstName: e.target.value // このプロパティだけ上書き
});
state内の配列の更新
配列に要素を追加する
- オブジェクトの書き換え同様に、配列もイミュータブルなものとして扱う。
const [artists, setArtists] = useState([]);
// BAD: pushは配列の書き換えを行う。
artists.push({
id: nextId++,
name: name,
}
// GOOD
setArtists( // Stateの置き換え。set関数を使うことでReactが変更を検知できる。
[ // 新しい配列作成
...artists, // 配列の要素をコピー
{ id: nextId++, name: name } // 要素を追加
]
);
配列から要素を削除
- 削除したい要素を含まない新しい配列を作ってset関数に入れる
-
filter()
を使えば簡単。
-
[sampleArray, setSampleArray] = useState([
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye'},
{ id: 2, name: 'Louise Nevelson'}
]);
setArtists(
// filter()は書き換えではなく、新しい配列を返しているので問題ない。
artists.filter(a =>
a.id !== artist.id
)
);
配列の更新
- 指定した要素を置き換えた新しい配列を作ってset関数に入れる
-
map()
を使うと簡単
-
[sampleArray, setSampleArray] = useState([
{ id: 0, name: 'Marta Colvin Andrade' },
{ id: 1, name: 'Lamidi Olonade Fakeye'},
{ id: 2, name: 'Louise Nevelson'}
]);
const handleChange = () => {
// 一部を更新した新しい配列を作成
newSampleArray = sampleArray.map(item => {
if(id === 0) {
return {
...sampleArray,
name: 'Martin Ødegaard'
}
}
})
// set関数でstateを更新
setSameleArray(newSampleArray);
}