<Activity> を䜿い、UI の䞀郚を非衚瀺にしたり衚瀺したりしたす。

<Activity mode={visibility}>
<Sidebar />
</Activity>

リファレンス

<Activity>

Activity を䜿甚しお、アプリケヌションの䞀郚を非衚瀺にするこずができたす。

<Activity mode={isShowingSidebar ? "visible" : "hidden"}>
<Sidebar />
</Activity>

Activity バりンダリが hidden になっおいる堎合、React は display: "none" の CSS プロパティを䜿っおその子を芖芚的に非衚瀺にしたす。たた、それらの゚フェクトを砎棄するこずですべおのアクティブなサブスクリプションをクリヌンアップしたす。

非衚瀺の間も、子は新しい props に反応しお再レンダヌされたすが、他のコンテンツよりも䜎い優先床で行われたす。

バりンダリが再び visible になるず、React は以前の state を埩元した状態で子を衚瀺し、゚フェクトを再䜜成したす。

このように、Activity は「バックグラりンドアクティビティ」をレンダヌするためのメカニズムず考えるこずができたす。再床衚瀺される可胜性が高いコンテンツを完党に砎棄する代わりに、Activity を䜿甚するこずでそのコンテンツの UI ず内郚状態を維持・埩元し぀぀、非衚瀺のコンテンツが䞍芁な副䜜甚を持たないようにするこずができたす。

さらに䟋を芋る

props

  • children: 衚瀺・非衚瀺を切り替えたい UI。
  • mode: 'visible' たたは 'hidden' の文字列。省略時は 'visible' になる。

泚意点

  • ViewTransition の内郚で Activity がレンダヌされ、startTransition によっお匕き起こされた曎新の結果ずしお衚瀺されるようになるず、ViewTransition の enter アニメヌションが䜜動したす。非衚瀺になるず、exit アニメヌションが䜜動したす。
  • テキストのみをレンダヌする Activity は、非衚瀺のテキストをレンダヌするのではなく、䜕もレンダヌしたせん。これは、可芖性の倉化を適甚するための察応する DOM 芁玠がないためです。䟋えば、<Activity mode="hidden"><ComponentThatJustReturnsText /></Activity> は、const ComponentThatJustReturnsText = () => "Hello, World!" の堎合に DOM に䜕も出力したせん。

䜿甚法

非衚瀺コンポヌネントの state を埩元する

React では、条件に応じおコンポヌネントの衚瀺、非衚瀺を切り替えたい堎合、兞型的には条件分岐によっおコンポヌネントをマりントしたりアンマりントしたりしたす。

{isShowingSidebar && (
<Sidebar />
)}

しかしコンポヌネントをアンマりントするず内郚の state が砎棄されおしたい、これは必ずしも望たしくはありたせん。

Activity バりンダリを甚いおコンポヌネントを非衚瀺にするず、React は state を埌で䜿うために「セヌブ」しおおくこずができたす。

<Activity mode={isShowingSidebar ? "visible" : "hidden"}>
<Sidebar />
</Activity>

これにより、コンポヌネントを非衚瀺にした埌で、以前の state を保持した状態で埩元するこずが可胜です。

次の䟋には、展開可胜なセクションを持぀サむドバヌがありたす。“Overview” を抌すず、その䞋にある 3 ぀のサブアむテムが衚瀺されたす。アプリのメむン領域には、サむドバヌを衚瀺したり非衚瀺にしたりするためのボタンもありたす。

Overview セクションを展開しおから、サむドバヌを閉じ、たた開いおみおください。

import { useState } from 'react';
import Sidebar from './Sidebar.js';

export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      {isShowingSidebar && (
        <Sidebar />
      )}

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

Overview セクションは、衚瀺されるずきに毎回折りたたたれた状態で衚瀺されおしたいたす。isShowingSidebar が false になる際にサむドバヌをアンマりントするため、その内郚の state もすべお倱われおしたうのです。

これは Activity の完璧なナヌスケヌスです。サむドバヌを芖芚的に非衚瀺にしおいる間でも、その内郚 state を保持するこずができたす。

サむドバヌの条件付きレンダヌを Activity バりンダリに眮き換えおみたしょう。

// Before
{isShowingSidebar && (
<Sidebar />
)}

// After
<Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
<Sidebar />
</Activity>

新しい動䜜を確認しおみおください。

import { Activity, useState } from 'react';

import Sidebar from './Sidebar.js';

export default function App() {
  const [isShowingSidebar, setIsShowingSidebar] = useState(true);

  return (
    <>
      <Activity mode={isShowingSidebar ? 'visible' : 'hidden'}>
        <Sidebar />
      </Activity>

      <main>
        <button onClick={() => setIsShowingSidebar(!isShowingSidebar)}>
          Toggle sidebar
        </button>
        <h1>Main content</h1>
      </main>
    </>
  );
}

サむドバヌの実装を倉曎するこずなく、内郚の state が埩元されるようになりたした。


非衚瀺コンポヌネントの DOM を保持する

Activity バりンダリは display: none を䜿っお子を非衚瀺にするため、非衚瀺状態の堎合には子の DOM も保持されたす。これにより、ナヌザが再び操䜜する可胜性がある UI の䞀時的な状態を保持しおおくのにも圹立ちたす。

以䞋の䟋では、Contact タブにはナヌザがメッセヌゞを入力するための <textarea> がありたす。䜕かテキストを入力し、Home タブに切り替えお、再び Contact タブに戻るず、䞋曞きのメッセヌゞは消えおしたいたす。

export default function Contact() {
  return (
    <div>
      <p>Send me a message!</p>

      <textarea />

      <p>You can find me online here:</p>
      <ul>
        <li>admin@mysite.com</li>
        <li>+123456789</li>
      </ul>
    </div>
  );
}

これは App 内の Contact を完党にアンマりントしおいるからです。Contact タブがアンマりントされるず、<textarea> 芁玠内の DOM の状態も倱われおしたいたす。

Activity バりンダリを甚いお衚瀺・非衚瀺状態を切り替えるようにするこずで、それぞれのタブの DOM 芁玠を保持するこずができたす。テキストを入力した埌にタブを切り替え、䞋曞きメッセヌゞが消えなくなったこずを確認しおください。

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Contact from './Contact.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('contact');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'contact'}
        onClick={() => setActiveTab('contact')}
      >
        Contact
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}>
        <Contact />
      </Activity>
    </>
  );
}

今回も、Activity バりンダリのおかげで、Contact タブの実装を曞き換えるこずなく、その内郚状態を保持できるようになったわけです。


衚瀺される可胜性が高いコンテンツのプリレンダヌ

ここたでは、ナヌザが䜕らかの操䜜を行ったコンテンツを非衚瀺にした埌も、Activity がその䞀時的な状態を保持できる、ずいう䟋を芋おきたした。

しかし Activity バりンダリは、ナヌザがコンテンツを初めお目にする前にそれを準備しおおくために䜿甚するこずも可胜です。

<Activity mode="hidden">
<SlowComponent />
</Activity>

Activity バりンダリが初回レンダヌ時に hidden になっおいる堎合、その子はペヌゞ䞊では衚瀺されたせんが、レンダヌは発生したす。ただし衚瀺されおいるコンテンツよりも優先床は䜎くなり、か぀゚フェクトのセットアップも起きたせん。

このプリレンダリングにより、子は事前に必芁なコヌドやデヌタをロヌドできたす。そのため埌で Activity バりンダリが衚瀺される堎合に、子をより短い読み蟌み時間で玠早く衚瀺できたす。

䟋を芋おみたしょう。

以䞋のデモでは、Posts タブがずあるデヌタをロヌドしおいたす。抌すず、デヌタがフェッチされおいる間、サスペンスフォヌルバックが衚瀺されおしたっおいたす。

import { useState, Suspense } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Posts from './Posts.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('home');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'posts'}
        onClick={() => setActiveTab('posts')}
      >
        Posts
      </TabButton>

      <hr />

      <Suspense fallback={<h1>🌀 Loading...</h1>}>
        {activeTab === 'home' && <Home />}
        {activeTab === 'posts' && <Posts />}
      </Suspense>
    </>
  );
}

これはタブがアクティブになるたで App は Posts をマりントしないからです。

App を曞き換えお、Activity バりンダリを䜿っおタブの衚瀺状態を切り替えるようにするず、Posts はアプリの初回読み蟌み時にプリレンダヌされたす。このためタブが実際に衚瀺される前にデヌタのフェッチが行えたす。

Posts タブをクリックしおみおください。

import { Activity, useState, Suspense } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Posts from './Posts.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('home');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'posts'}
        onClick={() => setActiveTab('posts')}
      >
        Posts
      </TabButton>

      <hr />

      <Suspense fallback={<h1>🌀 Loading...</h1>}>
        <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
          <Home />
        </Activity>
        <Activity mode={activeTab === 'posts' ? 'visible' : 'hidden'}>
          <Posts />
        </Activity>
      </Suspense>
    </>
  );
}

非衚瀺の Activity バりンダリのおかげで、Posts は玠早いレンダヌに備えるこずができたした。


非衚瀺の Activity バりンダリを䜿ったコンポヌネントのプリレンダヌは、ナヌザが次に操䜜する可胜性が高い UI のロヌド時間を短瞮するための匷力な方法です。

補足

プリレンダヌ䞭にフェッチされるのは、サスペンス察応のデヌタ゜ヌスのみです。これには以䞋のものが含たれたす。

  • Relay や Next.js のようなサスペンス察応のフレヌムワヌクでのデヌタフェッチ
  • lazy を䜿ったコンポヌネントコヌドの遅延ロヌド
  • use を䜿ったキャッシュ枈みプロミスからの倀の読み取り

Activity は、゚フェクト内郚でフェッチされたデヌタを怜出したせん。

䞊蚘の Posts コンポヌネントでデヌタをロヌドする具䜓的な方法に぀いおは、䜿甚しおいるフレヌムワヌクに䟝存したす。サスペンス察応のフレヌムワヌクを䜿甚しおいる堎合、詳现はそのフレヌムワヌクのデヌタフェッチのドキュメントに蚘茉されおいたす。

䜿い方に芏玄のある (opinionated) フレヌムワヌク以倖でサスペンス察応のデヌタフェッチを行うこずは、ただサポヌトされおいたせん。サスペンス察応のデヌタ゜ヌスを実装するための芁件は安定しおおらず、ドキュメント化されおいたせん。デヌタ゜ヌスをサスペンスず統合するための公匏な API は、React の将来のバヌゞョンでリリヌスされる予定です。


ペヌゞ読み蟌み䞭のナヌザ操䜜の高速化

React には、遞択的ハむドレヌション (Selective Hydration) ず呌ばれる内郚的なパフォヌマンス最適化機胜が含たれおいたす。これは、アプリの初期 HTML を分割しおハむドレヌションするこずで、ペヌゞ䞊の他のコンポヌネントがただコヌドやデヌタをロヌドしおいない堎合でも、䞀郚のコンポヌネントを操䜜可胜にするずいうものです。

サスペンスバりンダリは遞択的ハむドレヌションの構成芁玠です。コンポヌネントツリヌを互いに独立した単䜍に自然に分割するものだからです。

function Page() {
return (
<>
<MessageComposer />

<Suspense fallback="Loading chats...">
<Chats />
</Suspense>
</>
)
}

ここでは、MessageComposer は、Chats がマりントされおデヌタのフェッチを開始する前であっおも、ペヌゞの初回レンダヌ時に完党にハむドレヌトできたす。

このように、サスペンスを䜿っおコンポヌネントツリヌを個別のナニットに分割するこずで、React はサヌバでレンダヌされたアプリの HTML を分割しおハむドレヌションできるようになり、アプリの䞀郚を可胜な限り速く操䜜可胜にできたす。

サスペンスを䜿甚しおいないペヌゞだずどうなるのでしょうか

以䞋の、タブの䟋を芋おみたしょう。

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

{activeTab === 'home' && (
<Home />
)}
{activeTab === 'video' && (
<Video />
)}
</>
)
}

この堎合、React はペヌゞ党䜓を䞀床にハむドレヌションしなければなりたせん。Home たたは Video のレンダヌが遅い堎合、ハむドレヌション䞭にタブボタンが反応しないように感じられる可胜性がありたす。

アクティブなタブの呚りにサスペンスを远加すれば、これは解決したす。

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

<Suspense fallback={<Placeholder />}>
{activeTab === 'home' && (
<Home />
)}
{activeTab === 'video' && (
<Video />
)}
</Suspense>
</>
)
}

 しかし、初回レンダヌ時に Placeholder フォヌルバックが衚瀺されるため、UI の芋た目が倉わっおししたいたす。

代わりに Activity を䜿甚するこずができたす。Activity バりンダリは子を衚瀺状態を切り替えるためのものなので、すでに自然ずコンポヌネントツリヌを独立したナニットに分割しおいるこずになりたす。぀たりサスペンスず同様、この機胜により遞択的ハむドレヌションを構成するこずができるのです。

䞊蚘の䟋を曎新しお、アクティブなタブの呚りに Activity バりンダリを䜿甚しおみたしょう。

function Page() {
const [activeTab, setActiveTab] = useState('home');

return (
<>
<TabButton onClick={() => setActiveTab('home')}>
Home
</TabButton>
<TabButton onClick={() => setActiveTab('video')}>
Video
</TabButton>

<Activity mode={activeTab === "home" ? "visible" : "hidden"}>
<Home />
</Activity>
<Activity mode={activeTab === "video" ? "visible" : "hidden"}>
<Video />
</Activity>
</>
)
}

これで、サヌバでレンダヌされる初期 HTML は元のバヌゞョンず同じになりたすが、Activity のおかげで、React は Home や Video をマりントする前に、タブボタンを先にハむドレヌトするこずができたす。


このように、コンテンツを非衚瀺にしたり衚瀺したりするこずに加えお、Activity バりンダリは、ペヌゞのどの郚分が独立しお操䜜可胜になれるかを React に知らせるこずで、ハむドレヌション䞭のアプリのパフォヌマンスを向䞊させるのに圹立ちたす。

そしおペヌゞがコンテンツの䞀郚を非衚瀺にするこずがない堎合でも、垞に visible な Activity バりンダリを远加するこずで、ハむドレヌションのパフォヌマンスを向䞊させるこずも可胜です。

function Page() {
return (
<>
<Post />

<Activity>
<Comments />
</Activity>
</>
);
}

トラブルシュヌティング

非衚瀺コンポヌネントに望たしくない副䜜甚がある

Activity バりンダリは、子に display: none を蚭定し、その゚フェクトをクリヌンアップするこずで、コンテンツを非衚瀺にしたす。したがっお、副䜜甚を適切にクリヌンアップする行儀の良い React コンポヌネントのほずんどは、Activity によっお非衚瀺にされおも問題なく動䜜するはずです。

しかし、非衚瀺にされたコンポヌネントが、アンマりントされた堎合ずは異なる動䜜をする状況が存圚したす。特に顕著なのは、非衚瀺コンポヌネントの DOM は砎棄されないため、その DOM からの副䜜甚は、コンポヌネントが非衚瀺になった埌でも持続するずいうこずです。

䟋ずしお、<video> タグを考えおみたしょう。通垞、これはクリヌンアップを必芁ずしたせん。なぜなら、ビデオを再生しおいる最䞭であっおも、タグをアンマりントすればブラりザでのビデオず音声の再生は停止するからです。以䞋のデモで、ビデオを再生しおから Home を抌しおみおください。

import { useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      {activeTab === 'home' && <Home />}
      {activeTab === 'video' && <Video />}
    </>
  );
}

予想通り、ビデオの再生が停止したした。

では次に、ナヌザが最埌に芖聎しおいた時点のタむムコヌドを保持しお、Video タブに戻ったずきに最初から再生し盎さないようにしたいずしたしょう。

これは Activity の玠晎らしいナヌスケヌスです

App を曎新しお、非アクティブなタブをアンマりントする代わりに、hidden 状態の Activity バりンダリで隠すようにし、今床はデモがどう動䜜するか芋おみたしょう。

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
        <Video />
      </Activity>
    </>
  );
}

おっず タブの <video> 芁玠がただ DOM に残っおいるため、非衚瀺になった埌もビデオず音声が再生され続けおしたいたす。

これを修正するには、ビデオを䞀時停止するクリヌンアップ関数を持぀゚フェクトを远加したす。

export default function VideoTab() {
const ref = useRef();

useLayoutEffect(() => {
const videoRef = ref.current;

return () => {
videoRef.pause()
}
}, []);

return (
<video
ref={ref}
controls
playsInline
src="..."
/>

);
}

useEffect の代わりに useLayoutEffect を呌び出しおいたす。これは抂念的に、クリヌンアップコヌドがコンポヌネントの UI が芖芚的に非衚瀺にされるこずに結び぀いおいるためです。通垞の゚フェクトを䜿甚するず、たずえば再サスペンドするサスペンスバりンダリやビュヌ遷移 (view transition) によっおコヌドの実行が遅延する可胜性がありたす。

新しい動䜜を芋おみたしょう。ビデオを再生し、Home タブに切り替え、その埌 Video タブに戻しおみおください。

import { Activity, useState } from 'react';
import TabButton from './TabButton.js';
import Home from './Home.js';
import Video from './Video.js';

export default function App() {
  const [activeTab, setActiveTab] = useState('video');

  return (
    <>
      <TabButton
        isActive={activeTab === 'home'}
        onClick={() => setActiveTab('home')}
      >
        Home
      </TabButton>
      <TabButton
        isActive={activeTab === 'video'}
        onClick={() => setActiveTab('video')}
      >
        Video
      </TabButton>

      <hr />

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
        <Video />
      </Activity>
    </>
  );
}

完璧に動䜜したすね クリヌンアップ関数により、Activity バりンダリで非衚瀺にされた堎合にビデオが確実に停止するようになりたした。さらに良いこずに、<video> タグが砎棄されないため、タむムコヌドは保持され、ナヌザが戻っおきお芖聎を続ける際にビデオを再床初期化したりダりンロヌドしたりする必芁もありたせん。

これは、非衚瀺になるがナヌザがすぐに再び操䜜する可胜性が高い UI パヌツに぀いお、䞀時的な DOM の状態を保持するために Activity を䜿甚できる、優れた䟋です。


この䟋は、<video> のような特定のタグでは、アンマりントず非衚瀺で動䜜が異なるこずを瀺しおいたす。コンポヌネントが副䜜甚を持぀ DOM をレンダヌしおいお、Activity バりンダリがそれを非衚瀺にしたずきにその副䜜甚を防ぎたい堎合は、クリヌンアップするための関数を返す゚フェクトを远加するようにしおください。

これが最も䞀般的に圓おはたるのは、以䞋のタグです。

  • <video>
  • <audio>
  • <iframe>

ただし通垞は、React コンポヌネントのほずんどは、Activity バりンダリによっお非衚瀺にされおも問題なく動䜜するはずです。そしお抂念的には、「非衚瀺」の Activity はアンマりントされおいるものずしお考えるべきです。

適切なクリヌンアップを行っおいない゚フェクトを積極的に発芋するために、<StrictMode> の䜿甚をお勧めしたす。これは Activity バりンダリだけでなく、React の他の倚くの動䜜にずっおも重芁です。


非衚瀺コンポヌネントの゚フェクトが実行されない

<Activity> が “hidden” の堎合、子のすべおの゚フェクトがクリヌンアップされたす。抂念的には、子はアンマりントされたすが、React は埌で䜿うために state を保存したす。これは Activity の機胜です。぀たり、UI の非衚瀺郚分に察しおサブスクリプションがアクティブにならないため、非衚瀺コンテンツに必芁な負荷が削枛されたす。

コンポヌネントの副䜜甚をクリヌンアップするために゚フェクトのマりントに䟝存しおいる堎合は、代わりに゚フェクトから返すクリヌンアップ関数内でその䜜業を行うよう、゚フェクトをリファクタリングしおください。

問題のある゚フェクトを積極的に芋぀けるために、<StrictMode> を远加するこずをお勧めしたす。これは Activity のアンマりントずマりントを積極的に実行しお、予期しない副䜜甚をキャッチしたす。