React Hooks(useState | useReducer)  + TypescriptでサンプルWebアプリ

久しぶりにReact触りました。以前Reduxを使ったサンプルアプリを作りましたが、もっと手軽に作りたいと思いますので、今回は同様のアプリをReact Hooksで作成したいと思います。

以前のサンプルは[こちら](https://qiita.com/makishy/items/857bb838a2910eb34da3)

サンプルコードを書く前にReact Hooksの簡単なご紹介を。
React Hooksは[React 16.8](https://github.com/facebook/react/blob/master/CHANGELOG.md#1680-february-6-2019)から追加された新しい機能です。

Reactの特徴は再利用可能なコンポーネントが作成可能であることですが、ステートフルなコンポーネントはコンポーネント間での再利用が難しく、ステートの複雑なライフサイクルにより理解が困難なものとなっていました。
Hookの導入により1つのコンポーネントを複数の小さな関数に分割可能にし、よりシンプルなコンポーネントを作ることが可能となります。

Hookの説明については[公式](https://ja.reactjs.org/docs/hooks-intro.html)を参照してください。

# Hooksの説明
Hooksにはいくつかのフックが用意されています。

 -  useState：関数コンポーネントの中でローカルな state を使うために呼び出す。
 -  useEffect：関数コンポーネント内で副作用を実行することを可能にする。（componentDidMount, componentDidUpdate および componentWillUnmount と同様の目的で使う。）
 -  useContext：React のコンテクストをコンポーネントのネストなしに利用できる
 -  useReducer：複雑なコンポーネントのローカル state をリデューサ (reducer) を用いて管理できる
 
など

すべてのフックについては[公式 Hooks API リファレンス](https://ja.reactjs.org/docs/hooks-reference.html)を参照してください。


# コードを書く
では、早速コードを書きます。

create-react-appで最初に作成されたテンプレートのApp.tsxを修正します。（Reduxのサンプルと違いindex.tsxの修正は不要です。）

```src/App.tsx
import React from 'react';
import './App.css';
import { TopPage } from './components/TopPage'

const App: React.FC = () => {
  return (
    <>
      <TopPage />
    </>
  );
}

export default App;

```
## useStateを使って書く

TopPageコンポーネントをuseStateフックを使って実装します。
（TextInput、RadioInput、SubmitButton、ShowStateは以前のサンプルを流用しています。）

```src/components/TopPage.tsx
import React, { useState } from 'react'
import { TextInput } from './TextInput'
import { RadioInput } from './RadioInput';
import { SubmitButton } from './SubmitButton';
import { ShowState } from './ShowState';

export const TopPage: React.FC = () => {
    const [inputValue, setInputValue] = useState('')
    const [selectedValue, setSelectedValue] = useState('')
    const [clickCount, setClickCount] = useState(0)
    const handleInputValue = (value: string) => {
        setInputValue(value);
        console.log(value)
    }

    const handleSelectedValue = (value: string) => {
        setSelectedValue(value)
    }

    const submit = () => {
        setClickCount(clickCount + 1)
    }
    return (
        <>
            <TextInput title='入力' inputValue={inputValue} onChangeValue={handleInputValue} />
            <RadioInput title='ラジオ' selectedValue={selectedValue} onChangeValue={handleSelectedValue} />
            <SubmitButton title='Click me' onClick={submit} />
            <ShowState inputValue={inputValue} selectedValue={selectedValue} clickCount={clickCount} />
        </>
    )
}
```


Webアプリを起動すると以下のような画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/c33fa9c5-06ba-23df-039e-053fe501e604.png)


## useReducerを使って書く
次に同じ内容をuseReducerを使って書き直してみます。
useReducerはuseStateの代替品です。公式によると「通常、useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合や、前の state に基づいて次の state を決める必要がある場合です」とのこと。

では、早速書き換えてみます。

```TopPage.tsx
import React, { useReducer } from 'react'
import { TextInput } from './TextInput'
import { RadioInput } from './RadioInput';
import { SubmitButton } from './SubmitButton';
import { ShowState } from './ShowState';

interface TopPageState {
    inputValue: string,
    selectedValue: string,
    clickCount: number,
}
interface TopPageAction {
    type: ActionType,
    payload: TopPageState,
}
enum ActionType {
    ACTION_INPUT_TEXT = 'ACTION_INPUT_TEXT',
    ACTION_SELECT_RADIOBUTTON = 'ACTION_SELECT_RADIOBUTTON',
    ACTION_CLICK_BUTTON = 'ACTION_CLICK_BUTTON',
}

const initialState = { inputValue: '', selectedValue: '', clickCount: 0 }

const reducer: React.Reducer<TopPageState, TopPageAction> = (state: TopPageState, action: TopPageAction) => {
    switch (action.type) {
        case ActionType.ACTION_INPUT_TEXT:
            return {
                ...state,
                inputValue: action.payload.inputValue
            }
        case ActionType.ACTION_SELECT_RADIOBUTTON:
            return {
                ...state,
                selectedValue: action.payload.selectedValue
            }
        case ActionType.ACTION_CLICK_BUTTON:
            return {
                ...state,
                clickCount: action.payload.clickCount
            }
        default:
            throw new Error()
    }
}

export const TopPage: React.FC = () => {
    const [state, dispatch] = useReducer(reducer, initialState)

    const handleInputValue = (value: string) => {
        dispatch({ type: ActionType.ACTION_INPUT_TEXT, payload: { ...state, inputValue: value } });
        console.log(value)
    }

    const handleSelectedValue = (value: string) => {
        dispatch({ type: ActionType.ACTION_SELECT_RADIOBUTTON, payload: { ...state, selectedValue: value } });
    }

    const submit = () => {
        dispatch({ type: ActionType.ACTION_CLICK_BUTTON, payload: { ...state, clickCount: state.clickCount + 1 } });

    }
    return (
        <>
            <TextInput title='入力' inputValue={state.inputValue} onChangeValue={handleInputValue} />
            <RadioInput title='ラジオ' selectedValue={state.selectedValue} onChangeValue={handleSelectedValue} />
            <SubmitButton title='Click me' onClick={submit} />
            <ShowState inputValue={state.inputValue} selectedValue={state.selectedValue} clickCount={state.clickCount} />
        </>
    )
}
```

さっくりとReduxライクに書き換えできました。

React Hooksを利用することで非常に簡単でシンプルなコードが書けましたね。
React Hooksの登場で、従来鉄板だと思われていたReact + Reduxの使い所も変わって来そうですね。