【React】おなじみのカウンターをカスタムフック化してみた


# 概要

おなじみのカウンターアプリをカスタムフックとしてコンポーネントから処理を切り出してみたので共有しておきます。

# ソース

## CodeSandBox

https://codesandbox.io/s/zen-black-828c9

## Counterコンポーネント

```react:Counter.tsx
import React from "react";
import useCounter from "../customHooks/useCounter";

type CounterProps = {
  minCount: number;
  maxCount: number;
};

function Counter(props: CounterProps) {
  const { minCount, maxCount } = props;

  const { count, addCount, message: countMessage, resetCount } = useCounter({
    minCount: minCount,
    maxCount: maxCount,
    initMessage: "初期値"
  });

  return (
    <div>
      <div style={{ fontSize: 12 }}>
        ({minCount} ~ {maxCount})
      </div>
      <div>COUNT: {count}</div>
      <div>MESSAGE: {countMessage}</div>
      <div>
        <button
          onClick={() => {
            addCount(1);
          }}
        >
          {" +1 "}
        </button>
        <button
          onClick={() => {
            addCount(-1);
          }}
        >
          {" -1 "}
        </button>
        <button
          onClick={() => {
            resetCount();
          }}
        >
          RESET
        </button>
      </div>
    </div>
  );
}

export default Counter;
```

## useCounter

```react:useCounter.tsx
import React, { useState } from "react";

type UseCounterProps = {
  initCount?: number;
  initMessage?: string;
  minCount?: number;
  maxCount?: number;
};

type UseCounterReturn = {
  count: number;
  addCount: (value: number) => void;
  message: string;
  resetCount: () => void;
};

function useCounter({
  initCount = 5,
  initMessage = "",
  minCount = 0,
  maxCount = 10
}: UseCounterProps): UseCounterReturn {
  const [count, setCount] = useState<number>(initCount);
  const [message, setMessage] = useState<string>(initMessage);

  const addCount = (value: number) => {
    const nextCount = count + value;
    if (nextCount < minCount) {
      setMessage("最小値を下回ります");
    } else if (nextCount > maxCount) {
      setMessage("最大値を上回ります");
    } else {
      setMessage("成功");
      setCount(prev => prev + value);
    }
  };

  const resetCount = () => {
    setMessage("カウントリセット");
    setCount(initCount);
  };

  return { count, addCount, message, resetCount };
}

export default useCounter;
```

## カスタムフックって？

簡単な話、カスタムフックはただの関数コンポーネントで、hooksを利用し状態(state)や副作用(effect)を持つこともでき、使いまわすことも出来る。ただ、DOMを返す代わりにインターフェースを提供する、それだけの事です。コードが抽象化されるのはもちろん、カプセル化のような効果もあり、保守性があがります。

## カウンターは何が起きてる？

`useCounter`は`count`(カウントの値) `addCount`(カウント操作) `message`(結果メッセージ) `resetCount`(カウントリセット)を提供し、Counterコンポーネントでは提供された値とインターフェースを利用して表示させています。

ここでインスタンスが生成されます。

```react:Counter.tsx_抜粋
const { count, addCount, message: countMessage, resetCount } = useCounter({
  minCount: minCount,
  maxCount: maxCount,
  initMessage: "初期値"
});
```

ボタンがおされると、useCounterの提供している`addCount()`が走ります。

```react:Counter.tsx_抜粋
<button
  onClick={() => {
    addCount(1);
  }}
>
  {" +1 "}
</button>
```
useCounterの`addCount()`が実行され、状態が更新されます。

```react:useCounter.tsx_抜粋
const addCount = (value: number) => {
  const nextCount = count + value;
  if (nextCount < minCount) {
    setMessage("最小値を下回ります");
  } else if (nextCount > maxCount) {
    setMessage("最大値を上回ります");
  } else {
    setMessage("成功");
    setCount(prev => prev + value);
  }
};
```

伴いCounterのビューも更新されます。

```react:Counter.tsx_抜粋
<div>COUNT: {count}</div>
<div>MESSAGE: {countMessage}</div>
```

## 〆

まだまだ初心者なので、ツッコミどころがあればご指摘お願いします。