私が歌川です

@utgwkk が書いている

React Compilerの不具合を踏んだ

起こったこと

React Compilerを有効にした上で、以下のような ClientComponent コンポーネントをrenderすると、useMemo フックを呼び出している行で ReferenceError: func is not defined というエラーが発生する。

import { useMemo } from 'react';

function createHook(func: (x: string) => string) {
  const useData = () => {
    return useMemo(() => ['123'].map((x) => func(x)), []); // ReferenceError: func is not defined
  };
  return useData;
}

const useSomeData = createHook((x) => x);

export function ClientComponent() {
  const data = useSomeData();
  return <h1>Hello {data.join(' ')}</h1>;
}

React Compiler Playgroundに投げてみると、確かに生成されるコードがおかしい。以下のような関数が末尾で定義されているが、func 変数をうまく引き回せていない。

function _temp(x) {
  return func(x);
}

createHook 関数は、与えられた引数をもとにReactフックを作成して返す関数である。ロジックを共通化する目的で、フックを生成する関数を用意する実装パターンを実践している。実際に不具合を踏み抜いたコードはもうちょっと長く、最小の再現コードを作ったらこのような形に落ち着いた。

この実装パターンがReactのルールに反していたらこっそり教えてください。

原因

おそらくReact Compilerの既知の不具合。

github.com

回避方法

useData 関数の内部に use no memo ディレクティブを書いて、React Compilerによる最適化を行わないようにすることで回避できる。

function createHook(func: (x: string) => string) {
  const useData = () => {
    'use no memo';
    return useMemo(() => ['123'].map((x) => func(x)), []);
  };
  return useData;
}

今回の場合だと useData 関数をarrow functionではなく function 宣言で定義することでも回避できた。なんでこれで回避できるのかは不明。

function createHook(func: (x: string) => string) {
  function useData() {
    return useMemo(() => ['123'].map((x) => func(x)), []);
  };
  return useData;
}

しかしReact Compilerって夢みたいな技術でいいですよね。あとReact Compiler Playgroundの存在はissueを見にいって知りました。

時間のないサイト運営者リング