🙌

useMemoでzodパースを包むべきか

2024/06/17に公開

はじめに

Reactコンポーネントのレンダリング処理を最適化するため、重い処理をuseMemoフックのfactory内で実行することは有効です。
では、zodスキーマのパースはその重い処理に含まれるのでしょうか。

ここでは簡単な例を使って計算時間の計測を行います。

環境

M2 MacBook Pro
macOS 14.5

next@14.2.4
react-dom@18.3.1
react@18.3.1
typescript@5.4.5
zod@3.23.8

Google Chrome 126.0

計測方法

以下のようなコードを用意してReact Developer ToolsのProfilerでコンポーネントを比較します。

page.tsx
"use client";

import { useEffect, useMemo, useState } from "react";
import { z } from "zod";

const schema = z.enum(["hoge", "fuga", "piyo"]);

type Props = {
  value: string;
};

const ZodComponent = (props: Props) => {
  const parseResult = schema.safeParse(props.value);

  return parseResult.data;
};

const UseMemoComponent = (props: Props) => {
  const parseResult = useMemo(
    () => schema.safeParse(props.value),
    [props.value]
  );

  return parseResult.data;
};

const repeatNumber = 10000;

export default function Page() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount((count) => {
        if (count < repeatNumber) {
          return count + 1;
        } else {
          clearInterval(intervalId);
          return count;
        }
      });
    });

    return () => clearInterval(intervalId);
  }, []);

  return (
    <>
      {count}
      <ZodComponent value="" />
      <UseMemoComponent value="" />
    </>
  );
}

ZodComponentは渡された値をそのままパースし、UseMemoComponentはパースした値をuseMemoフックで記憶します。
Pageはこの2つのコンポーネントのレンダリングをrepeatNumber回ずつ繰り返します。

計測結果

以下は計測をそれぞれ5回実行した際の計算時間の平均です。

repeatNumber == 1000の場合

コンポーネント 計算時間
ZodComponent 0.34ms
UseMemoComponent <0.1ms

repeatNumber == 10000の場合

コンポーネント 計算時間
ZodComponent 0.32ms
UseMemoComponent <0.1ms

おわりに

今回は限られたケースの計測しか行いませんでした。
しかし、計測結果から以下のようなことが言えると思います。

  • zodスキーマが文字列の列挙型のように単純で、useMemodepsが変わらないのであれば、もちろんuseMemoを使った方が速いが、そのままパースしてもレンダリング回数に対して計算時間はあまり変わらない

zodスキーマがより複雑であったり、useMemodepsが頻繁に変わるケースなどでは更なる計測が必要です。

個人的には、パフォーマンスを気にせず、コードの可読性を高めるためにuseMemoは使わないべきだと思います。

Discussion