React の state の理解を確かめる其の二：stateの管理基礎編

# 前回までのあらすじ

[以前の記事](https://qiita.com/haruki-lo-shelon/items/3cc32be883a0acf8066c)でstateの基礎知識に関する内容をまとめました。そこでstateがどういう雰囲気のものなのかという概念を学び、基本的な使い方について触れました。
今回はそんなstateの本格的？な使い方を学びます。

JavaScriptすらちゃんと触った経験が浅いのにまだまだいっぱい覚えることあるけど、大丈夫かなぁ…。大丈夫かなぁじゃないねん、強い気持ちで覚えるっ。

なお、今回も[公式のチュートリアルサイト](https://ja.react.dev/learn)を参考にしており、そちらで学んだことのまとめ記事となりますので詳しくは公式チュートリアルをご参照ください。

# stateの管理基礎
## state を使って入力に反応する

いきなりですが、Reactは宣言型プログラミングである。
宣言型プログラミングとは、UI を細かく管理する（命令型）のではなく、視覚状態ごとに UI を記述することを意味する。

例えばjsだとイベントごとにUIの処理を分けて書くが、Reactは全パターンのUIを記述し、イベントハンドラで視覚状態を切り替え、視覚状態ごとに表示を分ける。
簡単にいうとjsは相対的な指示で、Reactは絶対的な指示である。
視覚状態とは例えば「入力中」や「送信中」などであり、例えば以下のようにして編集中かどうかを管理できる
            
```jsx
const [isEditing, setIsEditing] = useState(false);  // 視覚状態
const [name, setName] = useState('Jane');
// ...
return (
    // ...
    {isEditing ? (  // 編集中なら
      <input
        value={name}
        onChange={e => {
          setName(e.target.value)
        }}
      />
    ) : (  // そうでなければ
      <b>{name}</b>
    )}
    // ...
)
```

## state 構造の選択

stateを定義する際、意識すべきことがいくつかある。

### 関連する state をグループ化する

グループ化とは、オブジェクトや配列にまとめることを指す。
具体的には、座標などの常にセットになっている値や、事前にstateの数が決まっていないものはグループ化すべしということ。

### state の矛盾を避ける

例えば送信中と送信済の二つの状態をstateで管理するとき、
            
```jsx
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);
```
            
と二つ定義すると、考慮漏れなどで二つともがTrueになった場合矛盾を起こしてエラーが起こりかねないため一つのstateにまとめるべし。
            
### 冗長な state を避ける

例えば配列とその要素数を管理する場合

```jsx
const initialItems = [
  { id: 0, title: 'Warm socks' },
  { id: 1, title: 'Travel journal' },
  { id: 2, title: 'Watercolors' },
];

export default function TravelPlan() {
  const [items, setItems] = useState(initialItems);  // 配列のstate
  const [total, setTotal] = useState(items.length);  // itemsの合計数のstate
    ...
```
            
お互い連動するはずの`items`と`total`が両方stateになっているのはよくない。
このままだと`items`を増やせば`total`も`setTotal`で更新しないといけない。
`items`と`total`は一心同体なので矛盾を避けるためにもstateにしないほうがいい。

```jsx
// ...省略

export default function TravelPlan() {
    const [items, setItems] = useState(initialItems);
    
    const total = items.length;
```

これで`items`の数が増減すると自動的に`total`が更新されるようになる。
            
### state 内の重複を避ける

以下のように、同じオブジェクトを使って複数のstateを管理するのはよくない。

```jsx
const initialItems = [
  { title: 'pretzels', id: 0 },
  { title: 'crispy seaweed', id: 1 },
  { title: 'granola bar', id: 2 },
];

...

const [items, setItems] = useState(initialItems);  // initialItemsのstate
const [selectedItem, setSelectedItem] = useState(  // initialItemsの一要素のstate
    items[0]
);
```

これだと両方のstateを一緒に変更しなければ、`selectedItem`が`items`の要素に一致しない。
同じオブジェクトを継承しているからといって同期されるわけではない。（前回で触れたように、stateは定義された時点で作り替えられて独立して存在するものだから。）
全アイテムと選んだアイテムを管理するなら、以下のように対応できる。

```jsx
const [items, setItems] = useState(initialItems);
const [selectedId, setSelectedId] = useState(0);  // 要素は管理せずIDだけ管理する

const selectedItem = items.find(item =>
    item.id === selectedId
);
```

このように、データに矛盾が起きないようにオブジェクトは一括管理することを心がけよう。
よく考えたらReactでなくてもこの考え方は当然のものか。。
            
### 深くネストされた state を避ける

例えばこんな深いネストのオブジェクトはstateに含めては大変。
    
```jsx
export const initialTravelPlan = {
    id: 0,
    title: '(Root)',
    childPlaces: [{
        id: 1,
        title: 'Earth',
        childPlaces: [{
            id: 2,
            title: 'Africa',
            childPlaces: [{
                id: 3,
                title: 'Botswana',
                childPlaces: []
                }, {
                id: 4,
                title: 'Egypt',
                childPlaces: []
                }, {
                ...
```
    
実際、親・子の関係であるデータを扱うときにこうしたい気持ちはわかるが、このstateに変更を加えるとき、変更する箇所から上位レベル全てのオブジェクトをコピーしないといけない。（stateのオブジェクトが持つ情報は最上位レベルの構造のみだから。）
つまりフラットであることが最善であるのだ。
    
以下のようにすればフラットになる
    
```jsx
export const initialTravelPlan = {
  0: {
    id: 0,
    title: '(Root)',
    childIds: [1, 42, 46],  // 子オブジェクトのIDを持つことで親子関係が保たれる
  },
  1: {
    id: 1,
    title: 'Earth',
    childIds: [2, 10, 19, 26, 34]
  },
  2: {
    id: 2,
    title: 'Africa',
    childIds: [3, 4, 5, 6 , 7, 8, 9]
  }, 
  3: {
    id: 3,
    title: 'Botswana',
    childIds: []
  },
  4: {
    id: 4,
    title: 'Egypt',
    childIds: []
  },
    ...
```
    
とにかく、stateはできる限り簡素にするんだという強い気持ちを持つ。

## コンポーネント間で state を共有する
複数のコンポーネントにまたがって状態管理をしたいことはザラにあるだろう。複数のコンポーネントを協調動作させたい場合は、state を共通の親で定義すればよろしい。

そして、その共通の親から props 経由で情報を下に渡す。

最後に、子が親の state を変更できるよう、イベントハンドラを下に渡す。

例：`First input`に文字を入力すると、同時に`Second input`にも反映されるようにする

```jsx
import { useState } from 'react';

export default function SyncedInputs() {
  const [text, setText] = useState('');  // ここでstateを定義（Input関数内ではなく！）

  function handleChange(e) {  // イベントハンドラもここで定義
    setText(e.target.value);
  }

  return (
    <>
      <Input
        label="First input"
        value={text}
        onChange={handleChange}
      />
      <Input
        label="Second input"
        value={text}
        onChange={handleChange}
      />
    </>
  );
}

function Input({ label, value, onChange }) {  // stateとイベントハンドラを受け取るだけ
  return (
    <label>
      {label}
      {' '}
      <input
        value={value}
        onChange={onChange}
      />
    </label>
  );
}
```

## state の保持とリセット
いきなりだが、stateの独立単位はコンポーネントである。
JSXタグが一つだろうが複数だろうが、はたまた条件分岐でコンポーネントを返していようが、最終的なツリーで同じ位置にあるコンポーネントは同じものと見なされてその中にあるstateは保持されるし、別のコンポーネントとして定義されたらそれは別のstateとして新たに生成される。
つまりコンポーネントは、要素の順番や構成によって同一物かどうかを判断される。

以下の例では、とあるstateの定義を含むFormを表示し、二つの場合によって表示を分けている。
ただしFormは場合によって表示が変わってはいけないという状況。

ポイントは、`else`の方の`{null}`がなければstateが保持されないということ。

```jsx
if (flag) {
  return (
    <div>
      <p><i>flagがTrueならこの文章が表示される〜</i></p>
      <Form />  {/* div内の２番目の要素 */}
      ...
  );
} else {  // わかりやすさのためelseで記述
    return (
      <div>
        {null}  {/* これがないと上とは別のコンポーネントとみなされる */}
        <Form />  {/* div内の２番目の要素 */}
        ...
    );
}
```

では、同じツリー構造だけどstateを分けて管理したい場合はどうか。
結論、stateを保持するJSXタグにkeyを与えるとkeyごとに区別してstateを保持できる。
逆に言えば、異なる key を与えることでサブツリーの state をリセットするよう強制することができる。

以下の例では、同じJSXタグを使って同じツリー構造になっているにも関わらず、別々で管理される。
```jsx

function Field({ label }) {
const [text, setText] = useState('');
return (
...
}

...
<Field key="lastName" label="Last name" />  // keyで区別してstateを管理できる
<Field key="firstName" label="First name" />
...
```

ちなみに普通のタグにもkeyを与えられる

```jsx
<img key={image.id} src={image.src} />
```

これは例えば画像の読み込みに時間がかかる画面遷移を行ったとき、イベントハンドラによってstateが更新されてテキストは更新されたのに、画像だけしばらく前の画像が表示されてしまうのを防ぐのに有効。

keyとstateを紐づけておけば、stateが更新された瞬間に画像は一旦削除される。

あと、state定義を持つコンポーネント定義をネストさせてはいけない。さもないと上位のコンポーネントがレンダーされるたびににそれより下の階層の state が全てリセットされてしまう。
```jsx
import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');  // MyComponentがレンダーされるたびにリセットされてまうで

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

```

# まとめ

一回理解すると楽しいです。
ようやく公式チュートリアルのチャレンジ問題も解けるようになってきました。
次回はstateの管理応用編です。