🙌
useMemoでzodパースを包むべきか
はじめに
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スキーマが文字列の列挙型のように単純で、
useMemo
のdeps
が変わらないのであれば、もちろんuseMemo
を使った方が速いが、そのままパースしてもレンダリング回数に対して計算時間はあまり変わらない
zodスキーマがより複雑であったり、useMemo
のdeps
が頻繁に変わるケースなどでは更なる計測が必要です。
個人的には、パフォーマンスを気にせず、コードの可読性を高めるためにuseMemo
は使わないべきだと思います。
Discussion