はじめに
初めまして!
エンジニアになって数年、今まで本を読むだけでしたが、もっとプライベートで楽しみながら成長したい!自分が学んだ足跡を残していきたい!と思い記事を書きました!
最終的には自在に開発できるようになりたいと思っています。
いろいろな記事を参考にさせてもらっています。
その中でもこれってどういう意味?とかつまづいたところを念入りに書いていこうかと思います。
今回の目的
前回の続きです。今回は「state の管理(state構造の選択)」。被る部分もあると思いますが、後で振りかえって深掘りするきっかけになるのが理想です。細かく書くと大変なのであっさりめ。
使用したものや事前準備
・Macbook Pro
・Visual Studio Code
主に参考にさせていただいた記事
快適に変更やデバッグが行えるコンポーネント
stateをうまく構造化できているかが重要。
state構造の原則
コンポーネントでいくつstate変数を使うのか、データ構造の検討を行う必要がある。
目的は、ミスを入り込ませずにstateを容易に更新すること、全てのstateが同期した状態を保てること。
原則
1.関連するstateをグループ化する
2.stateの矛盾を避ける
3.冗長なstateを避ける
4.state内の重複を避ける
5.深くネストされたstateを避ける
・stateはできる限りフラットな構造にする
関連するstateをグループ化する
・2つ以上のstate変数を常に同時に更新する場合は、単一のstateにまとめるよう検討する
例えば、ポインタの座標をstateとする時は、一つのstateとしてまとめるとよい
//この場合、二つのstateを同時に更新する必要がある
const [x, setX] = useState(0);
const [y, setY] = useState(0);
//オブジェクトとして一つにまとめると一つに管理でき同時に更新可能
const [position, setPosition] = useState({ x: 0, y: 0 });
//オブジェクトにした場合、一つの値のみ変更するときはスプレット構文が必要なため注意
setPosition({ ...position, x: 100 })
stateの矛盾を避ける
どちらもtrueになることがありえないなど、stateを同時に変更しないと矛盾が生じる可能性の出る構成は避ける。
例えば、送信中のステータスと送信済みのステータスは別々に管理するせず、同時に更新すると良い。
// 別々管理で、送信中でなくなり送信済みになった場合
await sendMessage(text); //API呼び出し
setIsSending(false);
setIsSent(true);
// stateでステータスを管理できるようにすれば一つで済む
setStatus('sending');
await sendMessage(text); //API呼び出し
setStatus('sent');
// さらに定数宣言すれば、別々管理の時のように判別しやすくなる
const isSending = status === 'sending';
const isSent = status === 'sent';
冗長なstateを避ける
stateを合算させて新たなstateを作成するなどの冗長構成は避ける
// fullNameは、firstName,lastNameの合算
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// fullNameを更新するには、firstNameかlastNameの更新に合わせて合算する必要が出てくる
setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
// firstNameかlastNameが更新関数で更新された場合は再レンダリングが走る
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// 再レンダリングが走るのでstateでなくてもconstで画面の描画はリアルタイムに変化する
const fullName = firstName + ' ' + lastName;
またpropsをstateにコピーするのはバグの温床にもなります。
初期レンダリングのみ値が欲しいのであればこれでいいですが、親コンポーネントと同期したい場合、この書き方だと再レンダリングには対応しません。
// propsは初期レンダリングの時のみコピーされる
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
親コンポーネントと同期をとりたい場合は定数を使用すること。
function Message({ messageColor }) {
const color = messageColor;
state内の重複を避ける
同じデータが複数のstate変数で扱うと同期させることが困難になる場合がある
※内容は長かったため、公式を参照
// アイテムリストのstate
const [items, setItems] = useState(initialItems);
// ボタンを押下した時に選択したitemをセットする
const [selectedItem, setSelectedItem] = useState(
items[0]
);
上記のstate構成の場合、itemsを別のイベントハンドラで変更した場合、selectedItemと同期を取れなくなることが問題となる。(例えばitemsの中身を変えるinputフォームなど)
再レンダリングされるたびに再計算をするのであれば、stateで重複して管理する必要はなくconstで管理できるようにstateを構成する
// アイテムリストのstate
const [items, setItems] = useState(initialItems);
// ボタンを押下した時に選択したitemをセットする
const [selectedId, setSelectedId] = useState(0);
// 再レンダリングが走った時に再計算される
const selectedItem = items.find(item =>
item.id === selectedId
);
深くネストされたstateを避ける
下記のようにネストした場合、深いネストを削除する場合、それぞれのオブジェクトでスプレット構文を使用し対象のアイテムを削除しなければならない。
xport const initialTravelPlan = {
id: 0,
title: '(Root)',
childPlaces: [{
id: 1,
title: 'Earth',
childPlaces: [{
id: 2,
title: 'Africa',
childPlaces: [{
id: 3,
title: 'Botswana',
childPlaces: []
},
参考:
フラットで保つことにより、更新処理の冗長性が減る。
export const initialTravelPlan = {
0: {
id: 0,
title: '(Root)',
childIds: [1, 42, 46],
},
1: {
id: 1,
title: 'Earth',
childIds: [2, 10, 19, 26, 34]
},
2: {
id: 2,
title: 'Africa',
childIds: [3, 4, 5, 6 , 7, 8, 9]
},
終わりに
そろそろ、実践をやるべきかなと思いました。
次