React Hooks（useContext、useMemo、useCallback）


# はじめに
React Hooksの基礎について学んだことを2回に分けて記事にしました。

前回、[React Hooks（導入、Props、useState、useReducer、useEffect、axios）](https://qiita.com/zgoovw_/items/cbd398ee6ee7e3abaf8f)
今回、React Hooks（useContext、useMemo、useCallback）

###開発環境
React: 17.0.2
Node.js: 14.16.0
Visual Studio Code: 1.57.1

#useContext
useContextを使うことでpropsを使わずに親から任意のコンポーネントに簡単にデータを渡すことができます。
最初に親コンポーネントからひ孫コンポーネントにpropsでデータを渡す場合のコードです。

```js:親コンポーネント
import Child from "./components/Child";

function App() {
  return (
    <div>
      <Child data="データ" />
    </div> 
  );
}

export default App;
```
親コンポーネントから子コンポーネントへpropsを渡します。

```js:子コンポーネント
import Grandchild from './Grandchild'

const Child = ({data}) => {
    return (
        <div>
            <Grandchild data={data} /> 
        </div> 
    )
}

export default Child
```
受け取ったpsopsのデータをさらに子コンポーネントから孫コンポーネントへ渡します。

```js:孫コンポーネント
import GreatGrandchild from "./GreatGrandchild";

const Grandchild = ({data}) => {
    return (
        <div>
            <GreatGrandchild data={data} /> 
        </div> 
    )
};

export default Grandchild;
```
受け取ったpsopsのデータをさらに孫コンポーネントからひ孫コンポーネントへ渡します。

```js:ひ孫コンポーネント
const GreatGrandchild = ({data}) => {
  return <div>{data}</div>; 
};

export default GreatGrandchild;
```
ひ孫コンポーネントで受け取り、propsのデータをh1に渡して表示させています。

###useContextを使った場合

まずAppContext.jsというコンポーネントを作成します。
AppContext.jsでやることは以下の３行だけです。

```js:AppContext.js
import { createContext } from "react";

const AppContext = createContext();

export default AppContext;
```

次に親コンポーネントでAppContext.jsをインポートして、データを持たせたAppContext.Providerで囲みます。

```js:親コンポーネント
import Child from "./components/Child";
import AppContext from "./components/AppContext";

function App() {
  return (
    <AppContext.Provider value={"データ"}>
      <div>
        <Child />
      </div> 
    </AppContext.Provider> 
  );
}

export default App;
```

ひ孫コンポーネントでuseContextを使って、AppContextを経由してデータを受け取ります。

```js:ひ孫コンポーネント
import { useContext } from "react";
import AppContext from "./AppContext";

const GreatGrandchild = () => {
  const value = useContext(AppContext);
  return <div>{value}</div>;　
};

export default GreatGrandchild;
```
たったこれだけで親からひ孫へ簡単にデータが渡すことができます。

#useMemo

```js
const foo = useMemo(() => func(), [A]);
```

useMemo内の処理はAが変更した時にだけ、レンダー中に実行されます。
Aを指定しない場合は最初のレンダリングだけuseMemo内の処理が実行され、その後はキャッシュした値が返されます。

useMemoが必要なシーンを説明するために極端にパフォーマンスが悪いコード例を見てみます。
下記コードではボタンを押す度にに全く依存関係のないfooの中の処理も実行されます。
それが原因でブラウザーに表示されるカウントはボタンを押した後、少し遅れてプラスされます。

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

const AppMemo = () => {
  const [count, setCount] = useState(0);

  const bar = 0;

  const foo = () => {
    let i = 0;
    while (i < 1000000000) i++;
    return ;
  };

  return (
    <div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        {count}
      </button> 
    </div> 
  );
};

export default AppMemo;
```

ボタンを押したと時にfooがの処理が走らないように、下記のようにfoo内の処理をuseMemoで囲むだけで、最初のレンダリング後はwhileは走らず、キャッシュしたbazが返されるようになります。

```js
  const foo = useMemo(() => {
    let i = 0;
    while (i < 1000000000) i++;
    return bar;
  }, []);
```

下記のように第二引数の配列内にオブジェクトを指定することで、指定したオブジェクトに変更があったときに処理が走るよにできます。

```js
  const foo = useMemo(() => {
    let i = 0;
    while (i < 1000000000) i++;
    return bar;
  }, [bar]);
```

###React.memo

React.memoはpropsをキャッシュし無駄なレンダリングを無くします。

次のコードは親コンポーネントにボタンを押すたびにカウントされる２種類のボタンを作成し、2つの子コンポーネントDisplay.jsには表示名とCountButton.jsにボタンとカウント数を担当しています。

```js:App.js
import React, { useState } from "react";
import CountButton from "./components/CountButton";
import Display from "./components/Display";

function App() {
  const [like, setLike] = useState(0);
  const [unlike, setUnlike] = useState(0);

  const Like = () => {
    setLike((prevLike) => prevLike + 1);
  };

  const Unlike = () => {
    setUnlike((prevUnlike) => prevUnlike + 1);
  };

  return (
    <div>
      <Display name="like" value={like} /> 
      <CountButton func={Like}>いいね</CountButton> 
      <Display name="unlike" value={unlike} /> 
      <CountButton func={Unlike}>もう一息</CountButton> 
    </div> 
  );
}

export default App;
```

```js:Display.js
import React from "react";

const Display = ({ name, value }) => {
  console.log(name);
  return <div>{value}</div>; 
};

export default Display;
```

```js:CountButton.js
import React from "react";

const CountButton = ({ func, children }) => {
  console.log(children);
  return (
    <div>
      <button onClick={func}>{children}</button> 
    </div> 
  );
};

export default CountButton;
```

上記の例ではどちらの片方のボタンを押すとlike,unlike両方がレンダリングされ、Display.js、CountButton.jsのconsole.logが走っていることが確認できます。

React.memoを使って、propsの再レンダリングを防ぐことができます。
方法は簡単で再レンダリングを防ぎたいコンポーネントをexport時にReact.memoで囲うだけです。

```js:Display.js
import React from "react";

const Display = ({ name, value }) => {
  console.log(name);
  return <div>{value}</div>; 
};

export default React.memo(Display); // ここ
```

```js:CountButton.js
import React from "react";

const CountButton = ({ func, children }) => {
  console.log("log", children);
  return (
    <div>
      <button onClick={func}>{children}</button> 
    </div> 
  );
};

export default React.memo(CountButton); // ここ
```

これでDiplay.jsの方はconsole.logが走っていないことが確認できます。
ですが、まだCountButton.jsの関数funcは再レンダリングされています。
(片方のボタンを押したら両方の関数が走るということではなく、両方の関数がインスタンス化されてしまうということです)

#useCallback 
前項での問題、子コンポーネントに渡すコールバック関数の再レンダリングを防ぐためにuseCallbackを使用します。
際レンダリングさせたくない関数をuseCallbackで囲むだけです。

```js:App.js
import React, { useState, useCallback } from "react"; // useCallbackインポート
import CountButton from "./components/CountButton";
import Display from "./components/Display";

function App() {
  const [like, setLike] = useState(0);
  const [unlike, setUnlike] = useState(0);

  const Like = useCallback(() => { // useCallbackで囲む
    setLike((prevLike) => prevLike + 1);
  }, []); // 今回は必要ありませんが、stateによって関数内の処理に変更するような場合は第二引数の配列にstateを記入する

  const Unlike = useCallback(() => {
    setUnlike((prevUnlike) => prevUnlike + 1);
  }, []);

  return (
    <div>
      <Display name="like" value={like} /> 
      <CountButton func={Like}>いいね</CountButton> 
      <Display name="unlike" value={unlike} /> 
      <CountButton func={Unlike}>もう一息</CountButton> 
    </div> 
  );
}

export default App;
```