react hooksを使ったレンダリングの動き

# どのコンポーネントがレンダリングされるのか？
細かくrenderの動きを検証できていなかったので備忘としてまとめます。

# FCのレンダリング
単純なstate操作で再レンダリングさせてみて、子コンポーネントも再レンダリングされるか確認してみる。

## 単純なFC
とくに何も気にしない、ただのTestコンポーネントを作って、中にコンソールログを仕込んだ。
こうすることで再レンダリングされているかどうかをコンソールのログを見てチェックする。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <Test />
    </div>
  );
}

const Test = () => {
  console.log("Test");
  return <div>Test</div>;
};
```

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/e9b12cf9-df4c-1c5c-e372-7249305b71fb.gif)

親がレンダリングされるたびに、このコンポーネントもレンダリングされる。

## memoを使う
次に、React.memoを使うことで再レンダリングされないことを確認する。

```jsx
import React, { useState, memo } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [isRender, setIsRender] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <Test />
    </div>
  );
}

const Test = memo(() => {
  console.log("Test");
  return <div>Test</div>;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

![Test2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/0f1cc528-b4be-a0b2-7eff-cc225ff293ed.gif)

これだと、親が再レンダリングされても問題なく子はレンダリングされない。

## memoにpropsを渡す
propsの比較をmemoで行う。
isRender2っていうstateを作ってそれをTestのpropsとして渡すようにした。

```jsx
import React, { useState, memo } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [isRender, setIsRender] = useState(0);
  const [isRender2, setIsRender2] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <button onClick={() => setIsRender2(isRender2 + 1)}>increment2</button>
      <Test cnt={isRender2} />
    </div>
  );
}

const Test = memo(({ cnt }) => {
  console.log("Test");
  return <div>Test</div>;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

![Test3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/ab2613ec-9455-ea1b-1009-5115d3a24103.gif)

すると、意図したとおりisRender2が変化したときのみレンダリングが走る。

## contextを使う
hooksになってからcontextを使ってみようと思っているが、レンダリングがどのタイミングで動くのか検証してみた。
まずはシンプルにProviderのvalueを受け取ってみる。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <TestContext.Provider value={{ isRender }}>
        <Test />
      </TestContext.Provider>
    </div>
  );
}

const Test = () => {
  console.log("Test");
  const { isRender } = useContext(TestContext);
  return <div>Test</div>;
};
```

![Test$.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/0d3a2c61-5661-4c41-dcbc-d0c28f42ba23.gif)

これはmemoとかもしてないので、毎回レンダリングされるのは納得。

## contextとmemoを使う（propsなし）
Consumer側にmemoをつけるとどうなるか。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <TestContext.Provider value={{ isRender }}>
        <Test />
      </TestContext.Provider>
    </div>
  );
}

const Test = memo(() => {
  console.log("Test");
  const { isRender } = useContext(TestContext);
  return <div>Test</div>;
});
```

![Test4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/14a5273e-5a68-0de8-ea7a-33a76709825f.gif)

思ったとおりではあったが、再レンダリングされた。

## contextとmemoを使う（propsあり）
では比較対象のpropsを渡して、それの変化がない場合にどう動くのか見てみる

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const [isRender2, setIsRender2] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <button onClick={() => setIsRender2(isRender2 + 1)}>increment2</button>
      <TestContext.Provider value={{ isRender }}>
        <Test cnt={isRender2}/>
      </TestContext.Provider>
    </div>
  );
}

const Test = memo(({ cnt }) => {
  console.log("Test");
  const { isRender } = useContext(TestContext);
  return <div>Test</div>;
});
```

![Test6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/c4c41c07-897c-1614-844f-6edd5e3505a5.gif)

memoでpropsを比較して入るが、Providerから渡されるvalueが変化しても再レンダリングされるみたい。
なるほど。

## contextのProviderの値が変化しない場合
Providerから提供されるvalueが変化しない場合。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const [isRender2, setIsRender2] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <button onClick={() => setIsRender2(isRender2 + 1)}>increment2</button>
      <TestContext.Provider value={{ isRender2 }}>
        <Test />
      </TestContext.Provider>
    </div>
  );
}

const Test = memo(() => {
  console.log("Test");
  const { isRender2 } = useContext(TestContext);
  return <div>{isRender2}</div>;
});
```

![test8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/53810357-6f41-716d-9153-b2a2df6efd5b.gif)

想像と違って動く。
なんでだ。
Providerをmemoしてないから？
やってみる。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const [isRender2, setIsRender2] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <button onClick={() => setIsRender2(isRender2 + 1)}>increment2</button>
      <TestProvider isRender2={isRender2} />
    </div>
  );
}

const TestProvider = memo(({ isRender2 }) => {
  return (
    <TestContext.Provider value={{ isRender2 }}>
      <Test />
    </TestContext.Provider>
  )
});

const Test = memo(() => {
  console.log("Test");
  const { isRender2 } = useContext(TestContext);
  return <div>{isRender2}</div>;
});
```

![test9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/3724103b-49b6-4799-8aa6-1b4c2c33196e.gif)

こうすれば動かないのか。
reduxでconnectしたコンポーネントはPureComponentになるんだけど、contextはちゃんとmemoしてあげないとだめなんだねー。

## 子コンポーネントをmemo化して、useContextを使わない場合
気になったのでやってみる。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const [isRender2, setIsRender2] = useState(0);
  return (
    <div className="App">
      <div>{isRender}</div>
      <button onClick={() => setIsRender(isRender + 1)}>increment</button>
      <button onClick={() => setIsRender2(isRender2 + 1)}>increment2</button>
      <TestContext.Provider value={{ isRender2 }}>
        <Test />
        <Test2 />
      </TestContext.Provider>
    </div>
  );
}

const Test = memo(() => {
  console.log("Test");
  const { isRender2 } = useContext(TestContext);
  return <div>{isRender2}</div>;
});

const Test2 = memo(() => {
  console.log("Test2");
  return <div>Test2</div>;
});
```

![Test10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/2f03ac88-aec4-5727-f27f-33019b190389.gif)

これではっきりわかったのが、useContextを使った子コンポーネントは、Providerがmemoされてvalueの比較がされていない場合はレンダリングされるが、useContextを使っていないコンポーネントは何も影響を受けない。

# 関数の定義でのレンダリング
よくパフォーマンスを落とすと言われているやつ。
ダメなやつと、上で定義してみたやつと、usaCallback使ったパターン。
受け取るコンポーネントはmemo化している。

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const log = () => console.log("Test2");
  const log2 = useCallback(() => console.log("Test3"), []);
  return (
    <div className="App">
      <button onClick={() => setIsRender(isRender + 1)}>render</button>
      <Test log={() => console.log("Test1")} />
      <Test log={log} />
      <Test log={log2} />
    </div>
  );
}

const Test = memo(({ log }) => {
  log();
  return <div>Test1</div>;
});
```

![test11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/0a91c40e-83cb-0c39-949a-42e2efdf32fe.gif)

これは今までの検証から想像通り。
useCallback使ってないと、再定義されて別の関数として判定してしまう。

## useCallbackでmemoしてない場合
ちょっと気になったので検証
memoしなかったらレンダリングされちゃうよね？

```jsx
function App() {
  const [isRender, setIsRender] = useState(0);
  const log2 = useCallback(() => console.log("Test3"), []);
  return (
    <div className="App">
      <button onClick={() => setIsRender(isRender + 1)}>render</button>
      <Test log={log2} />
    </div>
  );
}

const Test = ({ log }) => {
  log();
  return <div>Test1</div>;
};
```

![test12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160041/c6f02b39-38b6-fb4e-cfce-1309b5118c5f.gif)


やっぱりされた。

# まとめ
レンダリングでパフォーマンスを上げたいと思ったときにはまず、
・momoを使ってみる
→他の記事では使わない場合のほうがパフォーマンスが上がるときもあるよう
・contextと使う場合はProviderをmemo化する
・関数を定義する場合は、useCallbackとmemoを併用する。