20251031

2025/10/31

React

<Activity>

React 19.2でリリースされた Activity コンポーネント。コンポーネントツリー上の Activity バウンダリとして機能する。

Activity バウンダリ配下のコンポーネントは表示/非表示およびレンダリングの優先度のコントロールが可能になる。

<Activity mode={isActive ? 'visible' : 'hidden'}>
  <ChildComponent>
</Activity>

mode propでが 'visible' の場合は表示され、 'hidden' の場合は非表示となる。非表示となった子孫コンポーネントは引き続きレンダリングされるものの、レンダリングの優先度が下がる。

表示/非表示の切り替えは単純に display: none !import の付与で行われている模様。

CSSで表示を切り替えているだけなので、非表示の状態でも子孫コンポーネントが持つステートはそのまま保持される。

ただし、非表示状態の子孫コンポーネントはuseEffectを処理しない。非表示になると同時にuseEffectはクリーンアップされる。

function Inner() {
  useEffect(() => {
	  console.log('do effect');
    setTimeout(() => {
      console.log('hello');
    }, 1000);
    return () => {
      console.log('cleanup effect');
    }
  }, []);

  return <div>aaa</div>;
}

function App() {
  const [showCounter, setShowCounter] = useState(false);

  return (
    <>
      <Activity mode={showCounter ? 'visible' : 'hidden'}>
        <Inner />
      </Activity>
      <button onClick={() => setShowCounter((showCounter) => !showCounter)}>
        toggle counter
      </button>
    </>
  )
}

上記の場合、コンポーネントが非表示から表示に変わるたびに InneruseEffect が処理される。

「UIを一時的に非表示にするが、その間は副作用を止めておきたい」場合に便利。

とはいえ use を使ったフェッチなどは動作する。あくまで useEffect を無効にする。

// useにはキャッシュ化にあるPromiseを使う必要がある
const cache = new Map<string, Promise<string>>();

// フェッチ→テキスト取得まで行うPromiseを返す
function fetchUrl(url: string): Promise<string> {
  let p = cache.get(url);
  if (!p) {
    p = fetch(url).then(res => res.text());
    cache.set(url, p);
  }
  return p;
}

function Inner() {
  // Activityが非表示状態でもフェッチする
  const data = use(fetchUrl('<https://dummyjson.com/test>'));
  return <><textarea defaultValue={JSON.stringify(data)} /></>;
}

function App() {
  const [showCounter, setShowCounter] = useState(false);

  return (
    <>
      <Activity mode={showCounter ? 'visible' : 'hidden'}>
        <Inner />
      </Activity>
      <button onClick={() => setShowCounter((showCounter) => !showCounter)}>
        toggle counter
      </button>
    </>
  )
}

まとめるとActivityでは

  • コンポーネントの表示/非表示を切り替えられる
  • 非表示になったコンポーネントはレンダリングの優先度が下がる
  • 非表示になったコンポーネントはuseEffectクリーンアップされる(表示時に再度実行される)

今後visibleとhidden以外のモードも追加する予定、とのこと。