← BLOG

続 React.memo なんていらんよね

2020-12-25


今回は具体的なアンチパターンを書いていこうと思います。

まず React.memo を使って OK なパターンです。

import { FC, memo, useEffect, useState } from "react";

type HogeProps = {
  hoge: string;
};

const Hoge: FC<HogeProps> = ({ hoge }) => {
  const piyo = "piyo";

  useEffect(() => {
    console.log(hoge, piyo);
  }, [hoge, piyo]);

  return <div>{hoge}</div>;
};

type FugaProps = HogeProps;

const Fuga: FC<FugaProps> = memo(Hoge);

const App: FC = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);
  }, []);

  return (
    <>
      <div>{count}</div>
      <Hoge hoge="hoge" />
      <Fuga hoge="fuga" />
    </>
  );
};

export default App;

この規模感程度で React.memo を使用したところで恩恵が得られるかはかなり怪しいため、普通は書かないと思いますが…。

対して、以下はアンチパターンです。(こんな書き方をする人はいないと思いますが…)

import { FC, memo, useEffect, useState } from "react";

type HogeProps = {
  hoge: string;
};

const Hoge: FC<HogeProps> = ({ hoge }) => {
  // ここが NG
  const piyo = { moge: "moge" };

  useEffect(() => {
    const { moge } = piyo;

    console.log(hoge, moge);
  }, [hoge, piyo]);

  return <div>{hoge}</div>;
};

type FugaProps = HogeProps;

const Fuga: FC<FugaProps> = memo(Hoge);

const App: FC = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);
  }, []);

  return (
    <>
      <div>{count}</div>
      <Hoge hoge="hoge" />
      <Fuga hoge="fuga" />
    </>
  );
};

export default App;

ポイントとしては piyo に格納される値の型です。

Hoge コンポーネントも Fuga コンポーネントも render が走るたびに piyo が生成されますが。

上記の書き方では piyo が生成されるたびに格納されるメモリの参照が変わってしまうため、render のたびに useEffect が走ります。

しかしアンチパターンにおける Fuga は React.memo でラップされているため、毎回固定値の fuga が渡されている以上、render は初回しか走りません。

したがって Fuga は Hoge と比較したときに render を抑止している挙動となるため、React.memo の用途として相応しくない、ということになります。

React.memo

これはパフォーマンス最適化のためだけの方法です。バグを引き起こす可能性があるため、レンダーを「抑止する」ために使用しないでください。

公式に書かれている通り、これはバグですね。

正しい挙動にするのであれば、piyo の生成に useMemo を挟んであげるべきではないでしょうか。

const piyo = useMemo(
  () => ({
    moge: "moge",
  }),
  [],
);

今回はめちゃくちゃわかりやすい例を書きましたが、実際に書くコンポーネントレベルとなると複雑度が段違いです。

そのため React.memo を本来の目的から外れた使い方をしていてもなかなか気づきにくいことは想像に難くないです。

その結果いつの間にかバグが発生している可能性も決して低くないため、安易に使うことはやはりおすすめできません。

やはりよほどのケースでない限り React.memo は使用する必要はないかなーと、個人的には思います。

← BLOG