はじめに
Reactの公式ドキュメントで参考になったページがあったので、自分の学習用にまとめました。
参考にしたページはこちら
Reactコンポーネントの状態(state)の構造を適切に設計することは、保守性に大きく影響を与えます。
状態を構築する際に考慮すべき5原則は以下です。
state構造設計のための5原則
- 関連する状態のグループ化
- 状態の矛盾を避ける
- 冗長な状態を避ける
- 状態内の重複を避ける
- 深いネストの状態を避ける
5原則それぞれ詳細について以下に記載します。
関連する状態のグループ化
例えば、コンピューターゲームのキャラクターを考えてみましょう。このキャラクターはx 座標とy 座標で移動できます。これら x と y の値を状態として管理するとしたら、どのように記述しますか?
悪い例
const [x, setX] = useState(0);
const [y, setY] = useState(0);
良い例
const [position, setPosition] = useState({ x: 0, y: 0 });
技術的には、どちらのアプローチも使用可能ですが、2つ以上の状態が常に同時に変化する場合は、1つの状態変数にまとめるのが良いでしょう。これにより、同期を保つことを忘れずに済みます。
状態の矛盾を避ける
例えば、メッセージングアプリを考えてみましょう。メッセージ送信の承認には、2つの異なるステージがあります。1つ目は「メッセージ送信中( isSending
)」、2つ目は「メッセージ送信済み( isSent
)」です。これらの2つの状態を、それぞれtrueやfalseのブール値として別々の状態変数で宣言すると、どのような問題が発生するでしょうか?
悪い例(状態の矛盾が起こる可能性がある)
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);
良い例
const [status, setStatus] = useState('typing');
// 'typing', 'sending', 'sent' のいずれか
この方法により、状態の矛盾を防ぎ、コードの可読性と保守性を向上させることができます。
冗長な状態を避ける
例えば、firstNameとlastNameからfullNameを計算できる場合、fullNameを状態として保持するのは冗長です。必要に応じてレンダー時に計算することで、状態の冗長性を避けられます。
悪い例
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
良い例
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
このように、計算可能な情報は状態として保持せず、必要に応じて計算することで、状態管理をシンプルに保つことができます。
状態内の重複を避ける
同じデータが複数の場所で管理されていると、更新時に不整合が生じる可能性があります。
悪い例(状態の重複あり)
以下のコードでは、items
配列と selectedItem
の両方で同じデータが管理されており、重複しています。
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(
items[0]
); // 重複
setItems(items.map(item =>
item.id === id ? { ...item, title: e.target.value } : item
));
良い例(重複を排除)
const initialItems = [
{ title: 'pretzels', id: 0 },
{ title: 'crispy seaweed', id: 1 },
{ title: 'granola bar', id: 2 },
];
export default function Menu() {
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);
const selectedItem = items.find(item =>
item.id === selectedId
);
このように、関連する情報を一つのオブジェクトにまとめて管理することで、データの重複や不整合を防ぐことができます。
深いネストの状態を避ける
状態が深くネストされていると、更新が複雑になり、バグの原因となります。可能な限り、状態はフラットな構造にし、必要に応じて複数の状態変数に分割することを検討してください。
悪い例
// 深くネストされた状態
const [state, setState] = useState({
user: {
name: '',
age: 0,
address: {
city: '',
zip: ''
}
}
});
// 更新が複雑になる例
setState(prevState => ({
...prevState,
user: {
...prevState.user,
address: {
...prevState.user.address,
city: 'Tokyo'
}
}
}));
良い例
// フラット化した状態管理の例
const [userName, setUserName] = useState('');
const [userAge, setUserAge] = useState(0);
const [userCity, setUserCity] = useState('');
const [userZip, setUserZip] = useState('');
この方法では、各値を直接更新できるため、保守性と可読性が向上します。
まとめ
Reactの状態管理の良い設計として、以下の点を押さえておきましょう
- 関連する状態のグループ化:複数の変数が同時に変更される場合はまとめる
- 状態の矛盾を避ける:矛盾しないよう、状態を一つの変数で管理する
- 冗長な状態を避ける:計算可能な値は状態として保持せず、計算する
- 状態内の重複を避ける:重複データを排除し、一元管理する
- 深いネストを避ける:状態のネストは浅く保ち、フラットに管理する
これらの原則を意識することで、コードの可読性と保守性が向上し、バグの少ないReactアプリケーションの構築に近づくことができると思います。
ぜひ参考にしてみてください!