FizzBuzzボタンを作りながらReact Hooksを学ぶ 第4回

FizzBuzz ボタンを作りながら React Hooks を学んでいきます。
目次は[こちら](https://qiita.com/masarutan/items/9ff33b4a32042b34a815)。

# Hooks を使って関数コンポーネントに書き換える

前回まででクラスコンポーネントを使う方法で、FizzBuzz ボタンを作りました。
今回はこれを React Hooks を使って書き換えていきます。
ようやく Hooks が出てきます。

`FizzBuzz` の `render()` の中身をそのまま持ってきて関数コンポーネントに改造していきます。

```tsx:FizzBuzz.tsx（修正前）
class FizzBuzz extends React.PureComponent<Props, State> {
  ...
  render() {
    console.log('render');
    const { count } = this.state;
    const { plus1, clear } = this;
    const message = fizzBuzz(count);

    return (
      <FizzBuzzView
        count={count}
        message={message}
        plus1={plus1}
        clear={clear}
      />
    );
  }
}
```

だいたいこんな↓感じです。
ここから必要な箇所を書き換えていきます。

```tsx:FizzBuzz.tsx（未完成版）
const FizzBuzz: React.FC<Props> = React.memo((props: Props) => {
  console.log('render');
  // const { count } = this.state;  // ① 状態管理
  // const { plus1, clear } = this; // ② ボタンを押したときの処理
  const message = fizzBuzz(count);

  return (
    <FizzBuzzView count={count} message={message} plus1={plus1} clear={clear} />
  );
});
```

# ① 状態管理には State Hook を使う

クラスコンポーネントでは状態の管理に `this.state` を使っていましたが、関数コンポーネントでは State Hook を使います。 

```tsx:FizzBuzz.tsx（未完成版、状態管理を実装）
import React, { useState } from 'react';

const FizzBuzz: React.FC<Props> = React.memo((props: Props) => {
  console.log('render');
  const [count, setCount] = useState(props.initialCount);
  // const { plus1, clear } = this; // ② ボタンを押したときの処理
  const message = fizzBuzz(count);

  return (
    <FizzBuzzView count={count} message={message} plus1={plus1} clear={clear} />
  );
});
```

`useState` は引数に初期値を取り、状態を表す変数と状態を変更する関数を配列で返します。

# ② ボタンを押したときの処理を実装する

```tsx:FizzBuzz.tsx（未完成版、コールバックを実装）
const FizzBuzz: React.FC<Props> = React.memo((props: Props) => {
  console.log('render');
  const [count, setCount] = useState(props.initialCount);
  const plus1 = () => setCount(count => count + 1);
  const clear = () => setCount(props.initialCount);
  const message = fizzBuzz(count);

  return (
    <FizzBuzzView count={count} message={message} plus1={plus1} clear={clear} />
  );
});
```

ボタンを押したときの処理では、State Hook で取得した `setCount` を使って count の値を変更します。

# コールバックを `useCallback` で包む

この実装では `FizzBuzz` が描画されるたびに `plus1`、`clear` の値が変わってしまいます。
そのため、`plus1`、`clear` を props で渡している `FizzBuzzView` が再描画されてしまいます。
これを防ぐために Callback Hook を使います。

```tsx:FizzBuzz.tsx（完成版）
import React, { useState, useCallback } from 'react';
import FizzBuzzView from './FizzBuzzView';
import { fizzBuzz } from './common';

type Props = {
  // props で count の初期値を受け取る
  initialCount: number;
};

const FizzBuzz: React.FC<Props> = React.memo((props: Props) => {
  console.log('render');
  const [count, setCount] = useState(props.initialCount);
  const plus1 = useCallback(() => setCount(count => count + 1), []);
  const clear = useCallback(() => setCount(props.initialCount), [
    props.initialCount,
  ]);
  const message = fizzBuzz(count);

  return (
    <FizzBuzzView count={count} message={message} plus1={plus1} clear={clear} />
  );
});

export default FizzBuzz;
```

`useCallBack` は第1引数で渡した関数をメモ化します。
ざっくり、一種のキャッシュと理解すれば良いと思います。メモリ上に関数を保持しておくことにより、再描画時に関数が再生成されず、最初に作成した関数が使い回されます。これにより `FizzBuzzView` の再描画が抑制されます。

第2引数には、第1引数の関数が依存している値の配列を渡します。
`plus1` は常に同じ関数を使用すれば良いので、依存している値はない、つまり `[]` を第2引数に指定します。
`clear` は `props.initialCount` が変われば別の関数を使う必要があります。
（正確ではないですが、
　`props.initialCount = 0` の場合は、`() => setCount(0)` を使い、
　`props.initialCount = 100` の場合は、`() => setCount(100)` を使うと考えれば理解しやすいです。）
そのため、`[props.initialCount]` を第2引数に指定します。

`useCallBack` の第2引数に関しては、create-react-app-ts.sh で作成したアプリならば、VS Code で保存するたびに適切なものが補完されますので、あまり気にする必要はありません。
（Create React App でアプリを作成すると、eslint-plugin-react-hooks がインストールされます。ここに修正のルールが書かれています。）

# App も関数コンポーネント化する

ついでに `App` も State Hook, Callback Hook を使って関数コンポーネント化しておきます。

```tsx:App.tsx
import React, { useState, useCallback } from 'react';
import FizzBuzz from './FizzBuzz';

const App: React.FC = () => {
  const [disable, setDisable] = useState(false);
  const hide = useCallback(() => setDisable(true), []);
  const show = useCallback(() => setDisable(false), []);

  return (
    <>
      {disable ? (
        <div>
          <button type="button" onClick={show}>
            show
          </button>
        </div>
      ) : (
        <div>
          <div>
            <button type="button" onClick={hide}>
              hide
            </button>
          </div>
          <FizzBuzz initialCount={0} />
        </div>
      )}
    </>
  );
};

export default App;
```

今回作成したソースコードは[こちら](https://drive.google.com/uc?export=download&id=1r2CPo35_e6eU9CmU5VFldMAOsyN0FMW0)。
次回はマウント、アンマウントされたときの処理を実装します。