React Labs: View Transitions、Activity、その他
公開日: 2025-04-23
In React Labs の投稿では、研究・開発が進行中のプロジェクトについて紹介します。本投稿では、本日すぐに試せる 2 つの新しい実験的機能のドキュメント公開と、現在開発中の他の領域に関するアップデートを共有します。
今回リリースする実験的機能(テスト可能):
- View Transitions
- Activity
現在開発中の新機能(アップデート):
- React Performance Tracks
- Compiler
- IDE Extension
- Automatic Effect Dependencies
- Fragment Refs
- Concurrent Stores
新しい実験機能
注: <Activity /> は react@19.2 にて提供済みです。<ViewTransition /> と addTransitionType は現在 react@canary で利用可能です。View Transitions と Activity は react@experimental でもテストできます。これらの機能はプロダクションでのテストを経て安定していますが、フィードバックに応じて最終的な API が変わる可能性があります。
試すには React パッケージを最新の experimental バージョンにアップグレードしてください:
react@experimental
react-dom@experimental
以下で、アプリでの使い方を説明するか、新しく公開されたドキュメントを確認してください:
<ViewTransition> : Transition のアニメーションを有効にするコンポーネント
addTransitionType : Transition の原因(type)を指定する関数
<Activity> : UI の一部を隠したり表示したりできるコンポーネント
View Transitions
React の View Transitions は、アプリ内の UI トランジションにアニメーションを追加するのを簡単にする新しい実験的機能です。内部的には、ほとんどのモダンブラウザで利用できる startViewTransition API を使用します。
要素をアニメーション対象にするには、新しい <ViewTransition> コンポーネントでラップします:
// "what" to animate.
<ViewTransition>
<div>animate me</div>
</ViewTransition>
このコンポーネントは、アニメーションが有効化されたときに「何を」アニメートするかを宣言的に定義できます。いつアニメートするかは、View Transition のトリガーとなる次の 3 つのうちいずれかを使って定義します:
startTransition(() => setState(...));
const deferred = useDeferredValue(value);
<Suspense fallback={<Fallback/>}>
<div>Loading...</div>
</Suspense>
デフォルトでは、これらのアニメーションはブラウザの View Transitions に適用されるデフォルトの CSS アニメーション(通常はスムーズなクロスフェード)を使用します。::view-transition 疑似セレクタを使って「どのように(how)」アニメーションするかを定義できます。例えば、すべてのトランジションのデフォルトアニメーションを変更するには:
// "how" to animate.
::view-transition-old(*) { animation: 300ms ease-out fade-out; }
::view-transition-new(*) { animation: 300ms ease-in fade-in; }
DOM が startTransition、useDeferredValue、または Suspense の fallback からコンテンツへの切り替えのようなアニメーショントリガーで更新されると、React は宣言的なヒューリスティクスによりどの <ViewTransition> コンポーネントをアクティブ化するかを自動で決定します。ブラウザは CSS で定義されたアニメーションを実行します。
ブラウザの View Transition API に詳しい方は、ドキュメントの「How does <ViewTransition> Work」を参照してください。
以下では、View Transitions の利用例をいくつか見ていきます。まずはアプリ(現状、以下の操作に対してアニメーションが無い例):
- ビデオをクリックして詳細表示
- 「戻る」をクリックしてフィードに戻る
- リストに入力してビデオをフィルタ
サンプルファイル: App.js、Details.js、Home.js、Icons.js、Layout.js、LikeButton.js、Videos.js、router.js
App.js (アニメーションなしのバージョン):
import TalkDetails from './Details';
import Home from './Home';
import { useRouter } from './router';
export default function App() {
const { url } = useRouter();
return url === '/' ? <Home /> : <TalkDetails />;
}
注意: View Transitions は CSS や JS ベースのアニメーションを置き換えるものではありません
View Transitions はナビゲーション、展開、開閉、並び替えなどの UI トランジションに使うことを意図しています。アプリのすべてのアニメーションを置き換える目的ではありません。上の例では、既に「いいね」ボタンや Suspense のフォールバックのグリマーにアニメーションがあります。これらは特定の要素をアニメートしているため、従来の CSS アニメーションが適切です。
ナビゲーションのアニメート
このアプリには Suspense 対応のルーターがあり、ページ遷移は既に startTransition を使うようマークされています:
function navigate(url) {
startTransition(() => { go(url); });
}
startTransition は View Transition のトリガーなので、ページ間のアニメーションに <ViewTransition> を追加できます:
// "what" to animate
<ViewTransition key={url}>{url === '/' ? <Home /> : <TalkDetails />}</ViewTransition>
URL が変わると <ViewTransition> と新しいルートがレンダリングされます。<ViewTransition> が startTransition 内で更新されたため、その <ViewTransition> はアニメーション用にアクティブ化されます。デフォルトでは View Transitions はブラウザのデフォルトのクロスフェードアニメーションを含みます。
アプリにこれを追加するだけで、ページ間のナビゲーション時にクロスフェードが行われます:
import { ViewTransition } from 'react';
import Details from './Details';
import Home from './Home';
import { useRouter } from './router';
export default function App() {
const { url } = useRouter();
return (
<ViewTransition>
{url === '/' ? <Home /> : <Details />}
</ViewTransition>
);
}
ルーターがすでに startTransition を使用している場合、<ViewTransition> を追加するだけでデフォルトのクロスフェードで動作します。詳しくはドキュメント「How does <ViewTransition> work?」を参照してください。
<ViewTransition> アニメーションをオプトアウトする方法
ここでは簡単のためにアプリルート全体を <ViewTransition> でラップしましたが、これだとアプリ内のすべてのトランジションがアニメートされ、予期せぬアニメーションが発生する可能性があります。これを防ぐには、ルートの子を default="none" でラップして各ページが自身のアニメーションを制御できるようにします:
<ViewTransition default="none">{children}</ViewTransition>
実際の運用では、ナビゲーションは enter/exit props や Transition Types を用いて行うべきです。
アニメーションのカスタマイズ
デフォルトでは <ViewTransition> はブラウザのデフォルトのクロスフェードを含んでいます。アクティベーションの仕方に応じて使用するアニメーションを <ViewTransition> の props で指定できます。例えばデフォルトのクロスフェードを遅くするには:
<ViewTransition default="slow-fade">
<Home />
</ViewTransition>
次に CSS 側で .slow-fade を view-transition クラスで定義します:
::view-transition-old(.slow-fade) { animation-duration: 500ms; }
::view-transition-new(.slow-fade) { animation-duration: 500ms; }
これによりクロスフェードが遅くなります。
サンプル: App.js と animations.css に定義があります。詳細は「Styling View Transitions」を参照してください。
Shared Element Transitions(共有要素トランジション)
2 つのページに同じ要素が含まれる場合、その要素をページ間でアニメートしたいことがあります。これを行うには <ViewTransition> にユニークな名前を付与します:
<ViewTransition name={`video-${video.id}`}>
<Thumbnail video={video} />
</ViewTransition>
これでビデオのサムネイルがページ間でアニメートします。
サンプル抜粋:
import { useState, ViewTransition } from "react";
import LikeButton from "./LikeButton";
import { useRouter } from "./router";
import { PauseIcon, PlayIcon } from "./Icons";
import { startTransition } from "react";
export function Thumbnail({ video, children }) {
return (
<ViewTransition name={`video-${video.id}`}>
<div aria-hidden="true" tabIndex={-1} className={`thumbnail ${video.image}`}>
{children}
</div>
</ViewTransition>
);
}
export function VideoControls() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<span className="controls" onClick={() => startTransition(() => { setIsPlaying(p => !p); })}>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</span>
);
}
export function Video({ video }) {
const { navigate } = useRouter();
return (
<div className="video">
<div className="link" onClick={(e) => { e.preventDefault(); navigate(`/video/${video.id}`); }}>
<Thumbnail video={video}></Thumbnail>
<div className="info">
<div className="video-title">{video.title}</div>
<div className="video-description">{video.description}</div>
</div>
</div>
<LikeButton video={video} />
</div>
);
}
デフォルトでは、React は遷移でアクティベートされた各要素に対してユニークな名前を自動生成します(詳細は「How does <ViewTransition> work」を参照)。React が、名前付きの <ViewTransition> が削除され、同じ名前の新しい <ViewTransition> が追加される遷移を検出すると、共有要素トランジションがアクティベートされます。詳細はドキュメント「Animating a Shared Element」を参照してください。
トランジションの原因に応じたアニメーション
トリガーの仕方に応じて異なるアニメーションを適用したい場合があります。そのために addTransitionType という新しい API を追加しました。これによりトランジションの原因(type)を明示できます:
function navigate(url) {
startTransition(() => {
addTransitionType('nav-forward');
go(url);
});
}
function navigateBack(url) {
startTransition(() => {
addTransitionType('nav-back');
go(url);
});
}
トランジションタイプを使うと、<ViewTransition> に渡す props 経由でトリガーに基づいたカスタムアニメーションを提供できます。例えばヘッダーの "6 Videos" と "Back" に対して共有要素トランジションを追加する場合:
<ViewTransition
name="nav"
share={{ 'nav-forward': 'slide-forward', 'nav-back': 'slide-back' }}
>
{heading}
</ViewTransition>
ここでは share prop を渡して、トランジションタイプに基づくアニメーションを定義しています。nav-forward からアクティブになった場合は slide-forward が適用され、nav-back の場合は slide-back が適用されます。
CSS でこれらのアニメーションを定義する例:
::view-transition-old(.slide-forward) { /* 前方にスライドする場合、"old" ページは左へスライドアウト */ }
::view-transition-new(.slide-forward) { /* 新しいページは右からスライドイン */ }
::view-transition-old(.slide-back) { /* 後方に戻る場合、"old" ページは右へスライドアウト */ }
::view-transition-new(.slide-back) { /* 新しいページは左からスライドイン */ }
(上記は説明用の概略です。具体的なアニメーション定義は Styling View Transitions を参照してください。)
この記事ではここまでで主に View Transitions の基本、カスタマイズ方法、共有要素トランジション、そして addTransitionType による原因ベースのアニメーション指定について説明しました。
次のセクションでは <Activity> を含む他の実験機能や、現在開発中の機能についてのアップデートを紹介します。
(注: 元記事の続きの具体的なコードや追加の CSS サンプルは、公式のドキュメントとサンプルリポジトリを参照してください。)