React hooks 【シュチュエーション別 hooks利用方法と使い方まとめ】

モダンReactの代表になっているhooksですが、使いこなせるととても便利です。
今まで、クラスコンポーネントや
SFC(Stateless Functional Component)として高階コンポーネントやレンダープロップス、Recomposeなどを使っていた方は使い勝手の良さに驚くでしょう。

また、これからReactを学習する人に向けてもhooksを使いこなすことはとても価値があり、
FC(Function Component)+hooksから入門するべきだと思います。
実際の現場でclassやSFCはレガシーであり、FC+hooksへの移行が進んでいるからです。

復習がてら、どんなhooksがあるのか、どんな時にどう使うのかをまとめました。

##前提として
**React version 16.8.0 以降**
hooksはFCでないと使用できない

```Hooks.jsx
import React, { useState, useEffect, useContext } from 'react'


const hooks = () => {
  //関数コンポーネント内にhooksを書く
   
  return null
}
```
読み込みが必要ですがここから先ははしょりますので、上記のように読込んでください。
または```React.hooksの名前```でも可能です。

##コンポーネント内で状態管理をしたい
###useState

```jsx
const hooks = () => {
  const [count, setCount] = useState(0)
  return(
    <>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>ボタン</button>
    </>
  )  
}
```
ステートフルな値と、それを変化させるための関数を分裂代入します。
上記は、ボタンを押すたびに```setCount```によって状態が変化して数字が増えていきます。

また、```useState```の第一引数はinitialState(初期値)となっていて、ロジックが含まれている場合は、関数を渡すこともできます。

```jsx
  const [count, setCount] = useState(()=> 1*2*3)
```

####オブジェクトの更新の仕方に注意
クラスコンポーネントの setState メソッドとは異なり、下記では正常にstateが更新されません。

```jsx
const hooks = () => {
  const [user, setUser] = useState({ name: "test", age: "20" })
  return (
    <>
      <div>{user.name}</div>
      <button onClick={() => 
        setUser({user.name = 'testNEW'})
      }>
        ボタン
      </button>
    </>
  )
}
```
consoleで```user```を出すと```testNEW```が出力されてしまいます。
正常に```name```の値のみ変更するためには、下記のようにスプレッド構文と併用してマージさせるように書きます。

```jsx
const hooks = () => {
  return (
    <button onClick={() => 
      setUser({...user, ...{ name: "testNEW"}})
    }>
      ボタン
    </button>
  )
}
```

##renderされた後に何か処理をしたい
###useEffect
いままで```componentDidMount```をつかって、render後にAPIリクエストを行うといったアクションを取っていたと思いますが、```useEffect```を使ってそれを実現できます。

正確に言うと```componentDidMount``` と ```componentDidUpdate``` と ```componentWillUnmount``` がまとまったものだと考えることができます。

```jsx
const hooks = () => {
  const [user, setUser] = useState({ name: "test", age: "20" })

  //APIリクエストのサンプルです
  const sampleGetRequest = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ name: "NEW" })
      }, 2000)
    })
  }
  //render後に発火
  useEffect(() => {
    sampleGetRequest().then(response => {
      setUser({ ...user, ...response })
    })
  }, [])

  return <div>{user.name}</div>
}
```

上記のコードを実行すると、renderされてから２秒後に
useStateで持っている、userが更新され、viewが書き換わります。

####第二引数について
第二引数を```引数なし、空の配列、値を入れた配列```で使い分けることによって、３種類の使い方ができます。

#####引数なし
```jsx
useEffect(() => {})
```
コンポーネントがマウントされた後、更新された後に関わらず、**毎回のレンダー時**に処理を実行します。
componentDidMount と componentDidUpdateを併用しているのと似ています。

#####空の配列
```jsx
useEffect(() => {}, [])
```
コンポーネントが初回にマウントされた後のみ処理を実行します。
componentDidMountを記述しているのと似ています。

#####値を入れた配列
```jsx
const [name, setName] = useState('test')
const [age, setAge] = useState(25)

useEffect(() => {
  console.log(`${name}は${age}才だ`)
}, [count])

```
配列内に入れた値を監視してくれます。
上記の例だと、配列の中に記入した```count```のデータの内容が書き換わった時に処理が実行されます。
なので、```age```のデータ内容が更新されても何も起きず、```count```が更新された時のみlogが出るようになります。

####無限ループに注意
引数を無し、配列に値を入れた引数の場合、無限ループになる可能性があります。
useEffectの説明の最初のコードの引数を無くしてみました。
これだと無限ループに陥って、処理を繰り返してしまいます。
```レンダー => setUser({ ...user, ...response }) => レンダー => setUser({ ...user, ...response }) => レンダー => 続く...```

```jsx
const sampleGetRequest = () => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ name: "NEW" })
      }, 2000)
    })
  }

useEffect(() => {
  sampleGetRequest().then(response => {     
    setUser({ ...user, ...response })
  })
})
```


なぜなら```setUser({ ...user, ...response })```によって```user```の参照は毎回切れてしまうからですね。
```{ name: "NEW", age: "20" }```から```{ name: "NEW", age: "20" }```に変更されても、値は変わってないように見えて、参照は変わっているので、仮想DOMの差分を検知して再レンダーしてuseEffect発火という流れができてしまいます。
空の配列を引数に指定すれば治ります。
useEffect内で状態を変化させる際は気をつけましょう。

もっとuseEffectについて詳し知りたい方は[useEffect完全ガイド](https://overreacted.io/ja/a-complete-guide-to-useeffect/)をご覧ください。

##コンポーネントツリー内で、値を共有したい
###useContext
通常だとデータを親から子に渡す際、propsとしてバケツリレーで渡していく必要があります。
ですが、useContextを使えば、バケツリレーの必要がなく、一気に下層コンポーネントに値を渡すことができます。
多くの階層を経由していくつかの props を渡すことを避けたいときはうってつけです。
クラスコンポーネントでもコンテクストは使えましたが、それのhooks版という感じです。
下記は、APP.jsxからTitle.jsxまで一気に(Header.jsxを飛び越えて)値を渡している例です。

```App.jsx
import React, { createContext } from "react"
import Header from "./Header"

//最上部のコンポーネントではクラスコンポーネントと同じように、
//値を共有するためのコンテクストをcreateContextで作成
export const PageMetaContext = createContext("");

const App = () => {
  const PageMeta = {
    title: "タイトル",
    description: "詳細です"
  };

  return(
  //Header以下のツリーに共有できるようにプロバイダを使用、
  //valueに値を設定することで、ツリー内のどの子コンポーネントにも渡せる(今回は PageMeta)
    <PageMetaContext.Provider value={PageMeta}>
      <Header />
    </PageMetaContext.Provider>
  )
}

export default App;
```
値を使用する必要のないコンポーネントは何も特別なことはしなくてもいい

```Header.jsx
import React from "react"
import Title from "./Title"

const Header = () => (
  <header>
    <Title />
  </header>
)

export default Header;
```
親のプロバイダで設定された値を使いたいコンポーネントのみ```useContext```を使う
注意すべき点は、```useContext``` に渡す引数は**コンテクストオブジェクト自体**であること

```Title.jsx
import React, { useContext } from "react";
//ツリーの親で作成されたコンテキストを読み込む
import { PageMetaContext } from "./App";

const Title = () => {
//ここでuseContextを使う
  const { title, description } = useContext(PageMetaContext);

  return (
    <>
      <h1>{title}</h1>
      <span>{description}</span>
    </>
  )
}

export default Title;
```

これで親で設定した```PageMeta```の中の文字列がviewに表示されます。

####useContext に渡す引数に注意
```useContext``` に渡す引数は**コンテクストオブジェクト自体**でなければいけません。

上記の例だと、titleのみ使いたいからといって、

```jsx
const title = useContext(PageMetaContext.title);
```
にするとエラーが起きてしまうので気をつけましょう。

##複数の複雑なstateを１つにして、templateをスッキリさせたい
###useReducer
useReducerはuseStateと同じく、コンポーネント内で状態管理をするためのhookで、
useStateの状態管理をより堅牢であり、複雑なロジックが絡んだステートを更新するのに適しています。
Reduxに馴染みがあれば簡単ですが、初見だとわかりにくく使用するのにも気がひけるので順をおって説明します。

下記のようなテキストボックスに入力するした値をリストにできるサンプルをつくりました。

![スクリーンショット 2020-06-20 19.58.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155363/c2fcb3bf-8996-eca4-5ba9-4ea9f90248eb.png)

２つのコンポーネントによって構成され、
下記はuseRecucerをもち、子コンポーネントからの入力によってリストをレンダーさせる親側のコンポーネントです。

```Todo.jsx
import React, { useReducer } from "react"
import InputArea from "./InputArea"

const initialState = { input: "", items: [] }

const reducer = (state, action) => {
  switch (action.type) {
    case "updateInput":
      return { ...state, input: action.payload };
    case "resetInput":
      return { ...state, input: "" };
    case "addItem":
      return { ...state, items: [...state.items, action.payload] };
    case "removeItem":
      const filteredItems = state.items.filter(v => v.key !== action.payload);
      return {
        ...state,
        items: filteredItems
      };
    case "resetItems":
      return initialState;
    default:
      throw new Error();
  }
}

const Lists = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <ul>
        {state.items.map((item, index) => (
          <li key={index}>
            {item.title}
            <button
              onClick={() =>
                dispatch({ type: "removeItem", payload: item.key })
              }
            >
              削除
            </button>
          </li>
        ))}
      </ul>
      <InputArea input={state.input} dispatch={dispatch} />
    </div>
  )
}

export default Lists
```


まずuseReducerの宣言部分ですが、

```jsx
const [state, dispatch] = useReducer(reducer, initialState);
```
- ```state``` ：ステートフルな値
- ```dispatch``` ：値を更新したいという旨をreducerに通知するための関数
- useReducerの第1引数の```reducer```：値を更新するためのロジックが書いてある関数
- useReducerの第2引数の```initialState```：値の初期値、関数でもOK。

実際にレンダリングのために扱う値は、```state```の中身になります。

値の更新は、```reducer```と ```action```の２つの重要な役割によって行われます。

####reducer

```jsx
const reducer = (state, action) => {
  switch (action.type) {
    case "updateInput":
      return { ...state, input: action.payload };
    case "resetInput":
      return { ...state, input: "" };
    case "addItem":
      return { ...state, items: [...state.items, action.payload] };
    case "removeItem":
      const filteredItems = state.items.filter(v => v.key !== action.payload);
      return {
        ...state,
        items: filteredItems
      };
    case "resetItems":
      return initialState;
    default:
      throw new Error();
  }
};
```
reducer関数は、actionのtypeによって行う処理を分岐させるため、switch文で書きます。

引数の中身は

- ```state```：現在のステート
- ```action ```：typeとpayloadが入ったオブジェクト
  - type：```"updateInput"```のような処理をするための名前
  - pyload：action関数から渡させる任意の値


今回のreducerの中には下記のしたい処理が書いてあります。

- inputの値を更新
- inputの値を空にする
- 配列にinputの値を追加
- 配列から、keyが同じオブジェクトを削除
- inputと配列の内容を初期化
- typeがない時エラーを投げる

それぞれのtypeごとに実行される関数から返される値は、**新しいステート**になります。
それにより、新しいレンダーが行われます。
そして何を元にtypeがわかり、処理が振分けされるのかというと、```action```によって行われます。

####action
```jsx
dispatch({ type: "removeItem", payload: item.key })
```
actionは簡単にいうと、reducerへの更新依頼です。
useReducerで宣言時に代入された```dispatch```を使うことで実現します。
```{ type: "removeItem", payload: item.key }```はreducer内のactionで使うことができます。

####下位コンポーネントでstateの更新をさせる
下記は先ほどのTodo.jsxの子コンポーネントで、テキストボックスから値を入力し、stateを更新する役割を持っているコンポーネントです。

```inputArea.jsx
import React from "react";

const InputArea = ({ input, dispatch }) => {
  const hundleChange = event => {
    dispatch({ type: "updateInput", payload: event.currentTarget.value });
  };

  const addItem = () => {
    dispatch({
      type: "addItem",
      payload: { title: input, key: new Date().getTime() + Math.random() }
    });
    dispatch({ type: "resetInput" });
  };

  const resetItems = () => {
    dispatch({ type: "resetItems" });
  };
  return (
    <div>
      <input type="text" onChange={e => hundleChange(e)} value={input} />
      <button onClick={addItem}>追加</button>
      <button onClick={resetItems}>リセット</button>
    </div>
  );
};

export default InputArea;

```

もし```useState```を使っている場合は親コンポーネントからコールバック関数を受け取り、発火させることでstateの更新ができましたが、
```useReducer```を使っている場合は、```dispatch```を渡し、子コンポーネント内でstate変更のactionを記述すれば良いので、stete更新のためのロジックが親コンポーネントに溜まらないので、見通しがよくなりますし、コンポーネントの役割が明確になります。
また、コンポーネントツリーが大きくなっている場合は、propsでなく、```useContext```と組み合わせて使えば、小規模アプリならreduxを使わずに、```useContext```＋```useReducer```でコードの肥大化を防ぐことができるでしょう。

##レンダーごとに計算を実行されないように処理をキャッシュしたい
###useMomo

useMemoはメモ化された**値**を返します。
メモ化というのはプログラムの高速化のための最適化する技術の１つで、処理結果を保持しておき、あるトリガーがあるまで処理を行わずに保持してある値を返すことを言います。

下記の例は```useMome```を使っていないコンポーネントで、

- テキストボックスの値を入力することで、stateの```input```が更新
- ボタンを押すと、stateの```count```の三乗が計算され、consoleが出る

というものです

```jsx
import React, { useState, useMemo } from "react";

const Memo = () => {
  const [count, setCount] = useState(2);
  const [input, setInput] = useState("");
 
  const newCount = () => {
    console.log("計算します");
    return Math.pow(count, 3);
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={e => setInput(e.target.value)}
      />
      <p>{newCount()}</p>
      <button onClick={() => setCount(prev => prev + 1)}>increment</button>
    </div>
  );
};

export default Memo;
```

上記だとonChangeが発火されるたびに、consoleに```計算します```が出力されます。
これは計算結果が変わることはないのに```newCount```関数が実行され、無駄な計算を繰り返していることになります。
今回の例は、そこまで複雑な計算ではないですが、もっと複雑になったり、他の処理も重なってくるとレンダリング速度のパフォーマンスの低下に繋がります。

ここで```useMome```を使います

```jsx
 const newCount = useMemo(() => {
    console.log("render");
    return Math.pow(count, 3);
 }, [count]);
```

これにより```newCount```はメモ化された値の入る変数になり、引数として配列に入っている```count```に変更があるまで処理は実行されません。
先ほどの問題は解消され、```onChange```が発火して、stateの```input```が書き換わっても、再度計算されなくなりました。

####useMemoの第二引数の指定に注意
- 引数なし：全てのstate, propに依存します。結果的に不必要に処理が行われるためメモ化する意味がなくなります。
- 空配列[]：何にも依存しません。処理が行われるのはレンダー直後のみになります。
- 値を入れた配列：配列内の変数に変更があるたびに、処理が実行されます。複数入力可能です。useMemoを使う場合は、忘れないようにしましょう。

##親からpropsとして渡される関数による無駄なレンダーを避けたい
###useCallback

簡単にいうと```useMome```の関数版です。
 ```useCallback```の場合はメモ化された**関数**を返します。