[React] 処理時間を確認するときのReact Developer Tools以外の選択肢

# はじめに
普段はReactの処理時間などを計測する際は、[React Developer Tools](https://ja.react.dev/learn/react-developer-tools)をよく使うと思いますが、[`<Profiler>`](https://ja.react.dev/reference/react/Profiler)でラップすることでも計測できることができます。

# やり方
サーバーコンポーネントでAPIなどで初期データを取得し、クライアントコンポーネントのテーブルに渡すことを考えます。クライアントコンポーネントのテーブルでは初期データの表示以外に`UPDATE`ボタンによってテーブルのデータの変更を行いテーブルの再レンダリングも行います。

情報を見たいコンポーネントを`<Profiler>`で囲うだけで確認できます。
今回はクライアントコンポーネントのテーブルの`TableBody`を確認します。
```tsx
<Profiler id="hoge" onRender={onRender}>{コンポーネント}</ Profiler>
```
`id`はプロファイルの識別子です。任意に設定でき、どのプロファイルかを追跡できます。
`onRender`はプロファイリング対象のツリー内のコンポーネントが更新された時に呼び出されるコールバック関数です。

例えば下記のようにすることでログを出すことができます
それぞれの値の意味は下記です。
- **id**: プロファイラに設定された識別子。

- **phase**: レンダリングのフェーズ。初回マウント時は `"mount"`、再レンダー時は `"update"` または `"nested-update"`。

- **actualDuration**: レンダリングに要した実際の時間（ミリ秒）。

- **baseDuration**: 最適化なしでサブツリー全体を再レンダーした場合の推定時間。メモ化している場合は、`actualDuration`と比較することでメモ化の効果を確認できます。

- **startTime**: レンダリングが開始されたタイムスタンプ。

- **commitTime**: 更新がコミットされた時刻のタイムスタンプ。

下記では`startTime`と`commitTime`の差分を取ることで全体の処理時間を出しています。

```typescript
function onRender(
  id: string,
  phase: "mount" | "update" | "nested-update",
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number
) {
  console.log(`Profiler ID: ${id}`);
  console.log(`Phase: ${phase}`);
  console.log(`Actual Duration: ${actualDuration} ms`);
  console.log(`Base Duration: ${baseDuration} ms`);
  console.log(`Start Time: ${startTime}`);
  console.log(`Commit Time: ${commitTime}`);
}
```



```tsx:page.tsx
import React from "react";
import CustomTable from "./CustomTable";
import { createRows } from "./createData";

const TablePage = async () => {
  const rows = createRows();

  return <CustomTable defaultRows={rows} />;
};
export default TablePage;


```


```tsx:CustomTable.tsx
"use client";

import React, { Profiler, useState } from "react";
import {
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  Button,
} from "@mui/material";
import { createRows } from "./createRows";
function onRender(
  id: string,
  phase: "mount" | "update" | "nested-update",
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number
) {
  console.log(`Profiler ID: ${id}`);
  console.log(`Phase: ${phase}`);
  console.log(`Actual Duration: ${actualDuration} ms`);
  console.log(`Base Duration: ${baseDuration} ms`);

  const totalDuration = commitTime - startTime;
  console.log(`Start Time: ${startTime} ms`);
  console.log(`Commit Time: ${commitTime} ms`);
  console.log(`Total Duration (全体時間): ${totalDuration} ms`);
}

interface RowType {
  id: number;
  name: string;
  age: number;
}
interface CustomTableProps {
  defaultRows: RowType[];
}
const CustomTable: React.FC<CustomTableProps> = (props) => {
  const { defaultRows } = props;
  const [rows, setRows] = useState(defaultRows);
  const handleClick = () => {
    const newRows = createRows();
    setRows(newRows);
  };
  return (
    <>
      <Button onClick={handleClick}>update</Button>
      <TableContainer component={Paper}>
        <Table>
          <TableHead>
            <TableRow>
              <TableCell>ID</TableCell>
              <TableCell>Name</TableCell>
              <TableCell>Age</TableCell>
            </TableRow>
          </TableHead>
          <Profiler id="hoge" onRender={onRender}>
            <TableBody>
              {rows.map((row) => (
                <TableRow key={row.id}>
                  <TableCell>{row.id}</TableCell>
                  <TableCell>{row.name}</TableCell>
                  <TableCell>{row.age}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Profiler>
        </Table>
      </TableContainer>
    </>
  );
};

export default CustomTable;



```


```ts:createRows.ts
const createData = (id: number, name: string, age: number) => ({
  id,
  name,
  age,
});

export const createRows = () =>
  Array.from({ length: 50000 }, (_, index) =>
    createData(index, `Name ${index}`, Math.floor(Math.random() * 100))
  );

```

# 結果の確認方法

## 初期データの表示
初期レンダリング時のconsole.logの結果は下記となりました。
`Phase`が`mount`となっていることがわかります
```
Profiler ID: hoge
CustomTable.tsx:24 Phase: mount
CustomTable.tsx:25 Actual Duration: 10656.60000003688 ms
CustomTable.tsx:26 Base Duration: 9273.400000020862 ms
CustomTable.tsx:29 Start Time: 25299.099999999627 ms
CustomTable.tsx:30 Commit Time: 36181.90000000037 ms
CustomTable.tsx:31 Total Duration (全体時間): 10882.800000000745 ms
```

## UPDATEボタンでの再レンダリング
再レンダリング時のconsole.logの結果は下記となりました。
`Phase`が`update`となっていることがわかります。
このデータから
Actual DurationがBase Durationと近い値なのでメモ化により最適化できそうと判断できます。
(今回の場合は、毎回`rows`の値が変わるのでメモ化の効果はなさそうです)
```
Profiler ID: hoge
CustomTable.tsx:24 Phase: update
CustomTable.tsx:25 Actual Duration: 23284.400000032037 ms
CustomTable.tsx:26 Base Duration: 23116.800000015646 ms
CustomTable.tsx:29 Start Time: 43257.90000000037 ms
CustomTable.tsx:30 Commit Time: 66795.59999999963 ms
CustomTable.tsx:31 Total Duration (全体時間): 23537.699999999255 ms
```

# まとめ
今回は<Profiler>を使って処理時間を見てみました。特定のコンポーネントだけサクッと確認したい時などに使えそうです。またReactのドキュメントによるとデフォルトは無効ですか本番環境でも有効にすることが可能なので、ステージングや本番で確認したい時に使えそうです。

# 参考
https://ja.react.dev/learn/react-developer-tools
https://ja.react.dev/reference/react/Profiler