SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

バックエンドエンジニアのためのフロントエンド入門

ReactからAPIを呼ぶ"裏側"について学ぼう——バックエンドエンジニアのためのフロントエンド開発

バックエンドエンジニアのためのフロントエンド入門 第3回

  • X ポスト
  • このエントリーをはてなブックマークに追加

Reactにおけるデータ取得の変遷

 本題に入る前に対象範囲を限定します。今回はブラウザがAPIサーバーにGETリクエストを投げてデータ取得をすることのみに焦点を当てています。いわゆるクライアントサイドレンダリング(CSR)におけるデータフェッチです。

 React Server Componentを利用しているNext.jsやReact Router v7(旧Remix)といったサーバーサイドでデータ取得が可能なフレームワークまで範囲を広げると話が複雑になるためここでは割愛します。GraphQLも本記事では対象外とします。

useEffectを使ったデータ取得

 useEffectはReact Hooksの一種で副作用を扱うものです。以下はデータ取得の実例です。

import { useEffect, useState } from 'react';

const POST_URL = 'https://jsonplaceholder.typicode.com/posts/1';

type ArticleType = {
  title: string;
  body: string;
};

function Article() {
  // 1. 状態を初期化する
  // 返り値は [状態(初期値はnull), 状態を更新する関数] の配列
  const [article, setArticle] = useState<ArticleType | null>(null);

  // 2. 副作用を扱う
  useEffect(() => {
    // 3. APIをコールする非同期処理
    async function fetchData() {
      const response = await fetch(POST_URL);
      const data = await response.json() as ArticleType;
      setArticle(data);
    }

    fetchData();
  }, []);

  // 4. loading...を描画する
  if (article === null) {
    return <div>loading...</div>;
  }

  // 5. APIから得た値とともにarticleを描画する
  return (
    <article>
      <header>
        <h1>{article.title}</h1>
      </header>
      <p>{article.body}</p>
    </article>
  );
}

 このコンポーネントは、記事を返すAPIにGETリクエストを送り、レスポンスを受け取ったら記事のタイトルと本文を表示するものです。レスポンスを受け取るまでは「loading...」という文字が画面に表示されます。

 コードを解説します。まず、状態管理のReact HooksであるuseStateを使って状態を初期化します。articleという値が管理したい状態であり、setArticleはこの状態を更新する関数です。

 なお、変数articleに値を再代入して直接更新することはできません。状態を更新する場合、必ずsetArticle(新しい値)というように更新関数を使う必要があります。

 useEffectの実行タイミングはコンポーネントの描画後です。Reactは状態の初期値でコンポーネントを一度描画した後、useEffectに渡された副作用を持つコールバック関数を処理します。

 このため、「1→4→初期描画(loading...)→2→3→5→再描画(記事のタイトルと本文)」の順番で処理されます。コードは上から書いた順番に実行されるわけではないのです。

 Reactコンポーネントは状態が更新されると再描画されます。3の中でsetArticleを実行しており、状態がnullからArticleType型の値に更新されました。Reactはこの新しい値を使ってコンポーネントを再実行します。よって、4の条件式がfalseになり、5の箇所が実行された結果が再描画されるのです。

 副作用を扱うuseEffectの挙動は一目で理解しづらく、React開発者にとって混乱の元でした。Reduxの開発者でありReactコアチームに在籍していたDan Abramov氏が、2019年に「useEffect完全ガイド」という長文ブログを公開して幅広く説明をしたものの、誤用はなくなりませんでした。

 現在ではuseEffectは本当に必要な場面でだけ使おう、無闇に使わないでおこうという考えが主流です。Reactの公式ドキュメントが「そのエフェクトは不要かも」という記事を公開しているほどです。

 それでも以下のデータフェッチ用のライブラリが登場するまでは、useEffectの中でデータを取得することが一般的でした。

ライブラリを使ったデータ取得

 useEffectを使ったデータ取得の問題点は、useEffect自体のややこしさだけではありません。ユーザー体験の面でも物足りないところがありました。

 例えば、ページAでAPI経由のデータ取得をした後、ページBに遷移します。ページBに遷移したときにページAのコンポーネントは破棄されるため、ページBからAに戻った時にはまたページAで再度APIをコールする必要があるのです。これでは一度遷移したページに戻るときもloading...という表示を挟まなければならならず、サクサクとページ遷移をするSPAらしい体験が損なわれてしまいます。

 この問題を解決するために、Vercel社が開発しているSWRやTanStack Query(旧React Query)というライブラリが登場しました。以下ではTanStack Queryの例を紹介します。

import { useQuery } from '@tanstack/react-query';

const POST_URL = 'https://jsonplaceholder.typicode.com/posts/1';

type ArticleType = {
  title: string;
  body: string;
};

async function fetchArticle(): Promise<ArticleType> {
  const response = await fetch(POST_URL);
  if (!response.ok) {
    throw new Error('Failed to fetch article');
  }
  return response.json();
}

function Article() {
  // useQueryはデータ取得と状態管理が一体になっているHooks
  const { data: article, error, isLoading } = useQuery({
    queryKey: ['article'],
    queryFn: fetchArticle,
  });

  if (isLoading) return <div>loading...</div>;
  if (error) return <div>エラーが発生しました: {error.message}</div>;

  return (
    <article>
      <header>
        <h1>{article.title}</h1>
        <span>投稿日: 2025/1/1</span>
      </header>
      <p>{article.body}</p>
    </article>
  );
}

 useEffectの例と大きく異なるのはuseState, useEffectが消え、代わりにuseQueryというライブラリ由来のHooksを使っている点です。

 useQueryはデータ取得と状態管理をセットで実施します。引数のオブジェクトにqueryFnというキーでAPIコールをする関数を渡し、queryKeyというキーにその関数とセットのキーを渡します(SWRも同じようなインターフェースを備えています)。TanStack Queryは中でJavaScriptのMapオブジェクトを使ってkey/value形式でクエリに関するデータをメモリに保持し、キャッシュとして扱います。

 この仕組みによって先ほどの課題は解決されました。TanStack Queryを使うと、ページAのHTTPレスポンスをメモリに保持します。ページBに遷移してからまたページAに戻ったとしても、先ほどのレスポンスのキャッシュデータを使ってページを表示します。

 このため、ローディング画面は表示されません。Reactは以前の状態を使ってコンポーネントを描画するため、if (isLoading) {/* ... */}のブロックを通らないのです。

 なお、ブラウザで実行されるJavaScriptのメモリにデータを保持しているだけなので、画面をリロードするとキャッシュは破棄され、再度画面ローディングが始まります。

 ただし、ここにはトレードオフがあります。先ほどの問題は解決しましたが、他にリトライや再検証(revalidate。最新のレスポンスを取得するために再度リクエストを送ること)、キャッシュの更新ロジックの管理など開発者がケアするべき他の点も生まれました。

 それでも、ユーザー側の利便性は向上した一方で開発者に求められることが増えただけと考えれば、ユーザーファーストなトレードオフであると言えるでしょう。

useを使ったデータ取得

 最後にuseを使ったデータ取得について触れておきます。useは2024年12月にリリースされたReact v19で導入された関数です。useにはJavaScriptの非同期関数の返り値であるPromiseを渡せるので、データ取得に使うことができます。記事執筆時点(2025年4月)でuseEffectを使わない最新のデータ取得方法は以下です。

import React, { use, Suspense } from 'react';

type Todo = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

async function fetchData(): Promise<Todo> {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
}

type Props = {
  data: Promise<Todo>;
};

function MyTodo({ data }: Props) {
  const fetchedData = use(data);
  return <div>{fetchedData.title}</div>;
}

export default function App() {
  const data = fetchData();
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyTodo data={data} />
    </Suspense>
  );
}

 useはPromiseの状態に応じてその処理を変えます。

 Promiseがpendingの場合、useはpromiseをthrowするため、親コンポーネントであるSuspenseのfallbackコンポーネント(<div>Loading...</div>)を表示します(Suspenseの詳しい説明は省略します)。

 Promiseがresolvedの時は、解決した値(ここではTODO型の値)を返すことでMyTodoコンポーネントが描画されます。反対にrejectedの場合は、エラーオブジェクトをthrowするため、ErrorBoundary(コード上では省略)でキャッチできるように準備します。

 データ取得のように、ReactにおいてuseEffectを使って非同期関数を実行する処理は、副作用と見なされると先ほど述べました。一方useを使うことで、純粋関数を志向するReactコンポーネントの中に副作用を扱う処理を自然に組み込めるようになったことがコードを見ても分かります。useEffectで行っていたように非同期処理を明示的に分離する必要がなくなったのです。これはReactの進化と呼べるでしょう。ただ、現場ではAPIでのデータ取得にTanStack QueryやSWRといったライブラリを使うことが主流であるため、開発者がuseを新たに使う機会は限定的かもしれません[3]

まとめ

 本記事ではReactにおけるデータ取得について紹介しました。同期処理と非同期処理の考え方の違い、またデータ取得の変遷を追うとReactとフロントエンドの関心ごとが分かるのではないかと思います。次の最終回ではフロントエンドのディレクトリ構成とコンポーネント開発を支えるツールについて紹介します。

  • [3]なお、useにはPromiseの解決以外にも利用方法があります。React公式ドキュメントuseも合わせてご確認ください。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
バックエンドエンジニアのためのフロントエンド入門連載記事一覧

もっと読む

この記事の著者

プログラミングをするパンダ(プログラミングヲスルパンダ)

 https://twitter.com/Panda_Program/ フロントエンドエンジニア。元々サーバーサイドエンジニアだったが、個人開発を機に HTML, CSS, JS に興味を持つ。特に React、Next.js に熱中しフロントエンジニアに転向。TDD、XP、DevOps が好き。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/21253 2025/04/22 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング