React.js: 状態（state）の構造を選択する


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3578596/72fcd8dc-1502-e4a8-c2f1-2b658a55a8d2.png)

# はじめに
Reactの公式ドキュメントで参考になったページがあったので、自分の学習用にまとめました。
参考にしたページは[こちら](https://react.dev/learn/choosing-the-state-structure)

---

Reactコンポーネントの状態（state）の構造を適切に設計することは、保守性に大きく影響を与えます。

状態を構築する際に考慮すべき5原則は以下です。

:::note
**state構造設計のための5原則**
1. 関連する状態のグループ化 
2. 状態の矛盾を避ける 
3. 冗長な状態を避ける
4. 状態内の重複を避ける
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の状態管理の良い設計として、以下の点を押さえておきましょう

1. 関連する状態のグループ化：複数の変数が同時に変更される場合はまとめる
2. 状態の矛盾を避ける：矛盾しないよう、状態を一つの変数で管理する
3. 冗長な状態を避ける：計算可能な値は状態として保持せず、計算する
4. 状態内の重複を避ける：重複データを排除し、一元管理する
5. 深いネストを避ける：状態のネストは浅く保ち、フラットに管理する

これらの原則を意識することで、コードの可読性と保守性が向上し、バグの少ないReactアプリケーションの構築に近づくことができると思います。

ぜひ参考にしてみてください！