as

Settings
Sign out
Notifications
Alexa
Amazonアプリストア
AWS
ドキュメント
Support
Contact Us
My Cases
開発
設計と開発
公開
リファレンス
サポート

並列レンダリングによるアプリのパフォーマンスの向上

並列レンダリングによるアプリのパフォーマンスの向上

並列レンダリングはReact 18の機能で、フレームワークが複数のUIバージョンを同時に準備できるようにします。Reactで並列レンダリングを使用すると、レンダリング操作を必要に応じて中断、一時停止、中止、または再開することができます。Reactでは、緊急性の高い更新かどうかを指定することで、メインスレッドをブロックせずに、複数の状態更新を一度に処理できます。緊急性の高いユーザー操作(入力、クリック、キーの押下)が優先的に処理され、緊急性の低い更新(大規模な画面の更新、データの取得)はバックグラウンドで処理されます。

並列レンダリングにより、Vegaアプリは次のことを行えます。

  • スムーズな再生とインタラクションの維持: UIコントロールとメディアは、負荷の大きい更新中も応答し続けます。
  • バックグラウンド処理の最適化: データの読み込みや計算によってインターフェイスがフリーズしません。
  • ナビゲーションの向上: 複雑なレンダリングタスクの実行中でも、画面間の切り替えをスムーズに行えます。

Vegaアプリでの並列レンダリングの概要は、こちらのビデオをご覧ください。

フック、コンポーネント、ヘルパー関数

useTransitionuseDeferredValueという2つのReactフックを使用すると、更新を管理したり、アプリのパフォーマンスを制御したりできます。不要な再レンダリングを防ぐため、これらのフックは、必ずコンポーネントのメモ化と組み合わせて使用してください。

Reactには、負荷の大きいコンポーネントのレンダリング中にフォールバックコンポーネントを表示するためのSuspenseコンポーネントも用意されています。lazyヘルパー関数を使用すると、負荷の大きいコンポーネントの読み込みを遅らせ、その後の更新に備えてキャッシュしておくことができます。

useTransition

useTransitionフックでは、状態更新を優先させて即座に処理するか、トランジション(優先度が低く、中断可能)として処理するかを指定できます。これにより、フィルタリング操作によってUIの更新が遅れるという、検索インターフェイスの一般的な課題が解決されます。デバウンスなどの手法を使用する代わりに、検索入力の応答性を維持しつつ、フィルタリングの優先度を低く設定できるようになりました。

このフックは、次の2つの値を返します。

  • isPending: 優先度の低い更新が保留中かどうかを表すブール値。読み込み状態を示す場合に役立ちます
  • startTransition: 緊急性が低い状態更新をラップする関数

例:

このサンプルコードでは、検索入力はsetQueryによって即座に更新されますが、フィルタリング操作はstartTransitionによって延期されます。トランジション中に読み込みインジケーターが表示され、準備ができた時点で結果が表示されます。

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // 検索入力を更新
    startTransition(() => {
      setResults(performSearch(query); // 検索操作を延期
    });
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};  

このコードが示すように、startTransitionは関数の実行を遅延させず、setResultsの更新を低優先度としてマークするだけです。Reactは、setQuery(高優先度のキュー)を最初に処理し、その後でsetResults(低優先度のキュー)を処理します。

このフックを直接使用できない場合は、startTransition関数を直接使用してください。

使用するタイミング: コンポーネントの状態変数を制御できる場合(検索インターフェイス、ナビゲーション、大規模なリストの更新)。

使用を避けるべきタイミング: コンポーネントの状態を直接制御できない場合(代わりにuseDeferredValueを使用します)

useDeferredValue

useDeferredValueフックを使用すると、重要な更新が完了するまで、プロパティや外部ソースの値の更新を遅らせることができます。たとえば、コンポーネントが検索結果を受け取ったとき、検索結果のプレビューを更新するという負荷の大きい処理を延期する一方で、見出しの検索語は即座に更新できます。

使用するタイミング: 状態変数にアクセスできない場合に、負荷の大きい計算や非同期操作をトリガーするプロパティ

使用を避けるべきタイミング: 状態変数を制御するコンポーネント(代わりにuseTransitionを使用します)

Suspenseとlazy

Suspenseコンポーネントは、(使用可能になった時点で)コードまたはデータの読み込み中にフォールバックコンテンツを表示します。lazy関数は、オンデマンドでコンポーネントをインポートします(必要に応じてコードを取得)。これらは単独で使用することも、一緒に使用することもできます。

Suspenseを単独で使用してデータを取得

この例では、MediaListがジャンルデータを取得してレンダリングしている間、Suspenseは読み込みプレースホルダー(MediaSkeleton)を表示します。

import { MediaAPI, Media, MediaCard } from "./api/mediaApi.ts";

type Props = { genre: string };

export const MediaList = ({ genre }: Props) => {
  const [media, setMedia] = useState<Media[]>([]);
  useEffect(() => {
    // プロミスの解決時間が長い
    MediaAPI.getGenre(genre)
        .then(setMedia)
        .catch((e) => console.error(e));
  }, [genre]);

  if (media.length===0) return null;

  return media.map((media: Media) => (
    <MediaCard key={media.id} media={media} />
  ));
};

lazySuspenseを併用し、既定の読み込み状態でコードを分割

この例では、LazyLoadedScreenのレンダリング時にMassiveComponentをインポートすると同時に、SuspenseMediaSkeletonプレースホルダーを表示します。

const MassiveComponent = lazy(() => import('./MassiveComponent'));

export const LazyLoadedScreen = () => {
  return (
    <View style={styles.container}>
      <Suspense fallback={<MediaSkeleton style={styles.skeleton} />}>
        <MassiveComponent />
      </Suspense>
    </View>
  );
};

並列レンダリングの実装

並列レンダリングを実装するには、次の手順に従います。

手順1: コンポーネントレベルのメモ化を追加

メモ化により、不要な再レンダリングと計算を防ぐことができます。コンポーネントの子では、useTransition()またはuseDeferredValue()を使用することが重要です。メモ化を行わないと、親が更新した時点で子が再レンダリングされるため、並列レンダリングのメリットが失われます。

メモ化しない場合:

この例のSearchComponentは、useTransitionをレンダリングする際、メモ化がどのように影響するかを示しています。メモ化しないと、結果に変更がない場合でも、キーストロークのたびにSearchResultsが再レンダリングされます。

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // 検索入力を更新
    startTransition(() => {
      setResults(performSearch(query)); // 検索操作を延期
    });
  };

  // キーストロークのたびにSearchResultsが再レンダリングされます
  const SearchResults = ({results}) => {
    return results.map(result => <Card data={result} />);
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};

メモ化する場合:

memoを使用すると、検索が変更されたときのみSearchResultsが再レンダリングされます。

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // 検索入力を更新
    startTransition(() => {
      setResults(performSearch(query)); // 検索操作を延期
    });
  };

  // 結果が変更されたときのみSearchResultsが再レンダリングされます
  const SearchResults = memo(({results}) => {
    return results.map(result => <Card data={result} />);
  });

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};

手順2: 優先度ベースの更新を適用

入力、クリック、スクロールなどの操作はすぐにユーザーにフィードバックする必要がありますが、データ処理やUIの更新は遅らせることができます。更新に優先順位を付けるには、useTransitionuseDeferredValueを使用します。

更新に優先順位を付けない場合:

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (query) => {
    setQuery(query);                    // 検索入力を更新
    setResults(performSearch(query);    // 検索中にUIをブロック
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      <SearchResults results={results} />
    </View>
  );
};

入力更新(setQuery)のたびに負荷の大きい操作(performSearch)が即座に実行され、両方の更新が完了するまでUIがブロックされます。キーストロークのたびに検索操作の完了を待つ必要があるため、ユーザーは入力時に遅延を感じます。

更新に優先順位を付ける場合:

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // 検索入力を更新
    startTransition(() => {
      setResults(performSearch(query); // 検索操作を延期
    });
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      // メモ化を忘れないこと
      <SearchResults results={results} />
    </View>
  );
};  

負荷の大きい操作(performSearch)がトランジションで実行されるようになったため、入力更新(setQuery)を即座に完了できます。検索結果がバックグラウンドで更新されている間、ユーザーはスムーズに入力できます。更新が保留されているときは読み込みインジケーターが表示されます。

テストと検証

並列レンダリングの実装をテストするには、以下のツールを使用します。

React DevToolsプロファイラー

React DevToolsプロファイラーを使用して、メモ化および優先更新の実装をテストします。

  1. Chromeで [React DevTools] を開きます。
  2. [Profiler] タブを選択します。
  3. 記録ボタンをクリックします。
  4. テストインタラクションを実行します(検索語句の入力、リストのスクロールなど)。
  5. 記録を停止します。
  6. フレームグラフに不要な再レンダリングやトランジションマーカーがないことを確認します。

Vega App KPI Visualizer

UIの応答性をテストするときは、Vega App KPI Visualizerを使用します。

Vega App KPI Visualizerを実行するには、次のコマンドを使用します。

kepler exec perf kpi-visualizer --app-name=[app-name] --kpi ui-fluidity --test-scenario <appiumを使用したpythonテストシナリオ>

滑らかさスコアが99%を超えており、並列レンダリングの実装後もスムーズなUIインタラクションを維持していることを確認します。


Last updated: 2025年11月5日