みどりのさるのエンジニア

Reactのパフォーマンス改善を体験できる練習問題を作ってみた

2022年12月13日

この記事はYAMAP エンジニア アドベントカレンダー2022 14日目の記事です。

業務でReactで実装されたフォームのパフォーマンス改善をしたので、その時の問題を一般化してパフォーマンス改善を体験できる練習問題を作ってみました。

練習問題

Reactで<input>要素がリストで表示されており、<input>要素にテキストを入力してみると非常にレスポンスが遅い状態になっています。このレスポンス速度を改善するのが今回の問題です。

ソースコードを眺めてみるとApp.tsxでTodoの一覧を管理しておりTotoListコンポーネントを描画しています。
onChangepropsにはsetTodosを渡しておりTodoの一覧を更新します。

// App.tsx
const initialTodos: TodoModel[] = [...Array(100)].map((_, i) => ({
  id: i,
  text: 'aaa',
}));

const App = () => {
  const [todos, setTodos] = useState(initialTodos);

  return (
    <div>
      <button
        onClick={() => {
          alert(todos[0].text);
        }}
      >
        保存
      </button>
      <TodoList todos={todos} onChange={setTodos} />
    </div>
  );
};

次にTodoListコンポーネントの実装を見てみます。

TodoListでは<input>要素としてTodoコンポーネントを描画しており、変更があったときに新しいTodoのオブジェクトを受け取り新しいTodoリストを引数としてpropsで渡されたonChangeコールバック関数を実行して、親要素に新しいTodoリストを渡しています。

export const TodoList: FC<TodoListProps> = ({ todos, onChange }) => {
  const handleChange = useCallback(
    (updatedTodo: TodoModel) => {
      const index = todos.findIndex((todo) => todo.id === updatedTodo.id);
      onChange?.([
        ...todos.slice(0, index),
        updatedTodo,
        ...todos.slice(index + 1),
      ]);
    },
    [onChange, todos]
  );

  return (
    <ul>
      {todos.map((todo) => {
        return (
          <li key={todo.id}>
            <Todo todo={todo} onChange={handleChange} />
          </li>
        );
      })}
    </ul>
  );
};

最後にTodoコンポーネントの実装を見てみます。

Todoコンポーネントではpropsで受け取ったtodoのテキストの内容を<input>要素のvalueとして表示しています。テキストが変更された時に新しいテキストでTodoオブジェクトを作成してonChange関数を実行して親要素に新しいTodoオブジェクトを渡しています。

[...Array()]はコンポーネントの描画速度を疑似的に遅くするために仕込んでいます。は問題を作成するための疑似コードなので、ここを削除しないでください。

export const Todo: FC<TodoProps> = memo(({ todo, onChange }) => {
  [...Array(100000)].forEach(() => 1 + 1);

  return (
    <input
      type="text"
      defaultValue={todo.text}
      onChange={(evt) => {
        onChange({
          ...todo,
          text: evt.target.value,
        });
      }}
    />
  );
});

このコンポーネント単体の描画速度は約10msほどになっており、実際の実装でも再現し得る問題になっています。

プロファイルの結果

解説

問題の解答は別の記事で書こうと思います。
興味があれば解いてみてください。