Menu

Reactの面接質問と回答集(React 19対応)

初級から上級までのReactの面接質問約30問に、面接官が納得する短い回答を付けました。読むより見たほうがわかりやすい質問には、動くコードも付いています。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

ここで紹介するのは、最もよく出るReactの面接質問を、レベル別にまとめ、面接官が期待する形で答えたものです。短く、React 19で正しく、ルールの裏にある理由も添えています。振る舞いを見たほうが読むより早いところにはライブエディタを付け、いくつかの質問では実行する前にコンソールの出力を予想してもらいます。

クリックする前に考えてください。プレビューの下のコンソールには何が出力され、ボタンには何が表示されるでしょうか?答えは下の初級の質問にあります。

初級の質問

Reactとは何ですか?

Reactは、コンポーネントからユーザーインターフェースを作るためのJavaScriptライブラリです。現在のデータに対するUIの記述を返す関数を書くと、データが変わったときに、Reactが何が変わったかを判断してDOMを更新してくれます。扱うのはビューの層だけで、ルーティング、データ取得、フォームはほかのライブラリやNext.jsのようなフレームワークが担当します。

JSXとは何ですか?

JSXは、JavaScriptの中にマークアップを書けるようにする構文拡張です。コンパイラ(Babel、esbuild、SWC)が各タグをReact要素を作る呼び出しに変えるので、<p className="note">{text}</p> は type: 'p' とpropsを持つオブジェクトになります。JavaScriptなので、class の代わりに className を使い、式は {} に入れ、すべてのタグを閉じる必要があります。ルールの詳細はJSXのページで扱っています。

コンポーネントと要素の違いは何ですか?

コンポーネントは、propsを受け取ってUIを返す関数(またはクラス)です。要素は、JSXが生み出す普通のオブジェクトで、何を描画するかを表します。<Avatar size={40} /> は、type が Avatar 関数である要素です。Reactはコンポーネントを呼んで要素をさらに別の要素に変え、最終的にDOMのタグにまで至ります。要素は安価でイミュータブルで、コンポーネントを自分で呼ぶことはありません。

propsとstateの違いは何ですか?

propsは親から渡され、コンポーネントの中では読み取り専用です。stateはコンポーネントが持ち、セッターを通じて時間とともに変わり、変わるたびに新しいレンダリングが予約されます。よく言われる表現では、propsはコンポーネントを設定するもので、stateはその記憶です。同じ値が、親ではstate、その子ではpropということもあります。

このページの最初の例は何をログに出しますか?

logged: 0 を出力し、ボタンは2ではなく Count: 1 を表示します。stateはスナップショットです。count はレンダリングの間ずっと定数なので、2回の setCount(count + 1) はどちらも 0 + 1 を計算し、新しい値は次のレンダリングにしか存在しないので、ログは古い値を読みます。2を足すには、更新関数の形 setCount((c) => c + 1) を2回使います。

リストにkeyが必要なのはなぜで、indexがよくないkeyなのはなぜですか?

Reactは key を使って、リストの各項目を前回のレンダリングの項目と対応させます。配列のindexを使って先頭に項目を挿入すると、すべてのindexがずれるので、Reactは各行のstateを古い位置に保ち、間違ったデータに結びつけてしまいます。

Adaにチェックを入れてから「Add to top」をクリックしてください。チェックが新しい1行目に移ります。key={index} を key={person.id} に変えてもう一度試すと、チェックはAdaに残ります。開発中はkeyがないとReactが警告も出しますが、プレビューは本番ビルドのように動くので、ここではその警告は表示されません。

制御された入力とは何ですか?

値がReactのstateから来る入力欄です。value を渡し、onChange でstateを更新するので、Reactが唯一の情報源となり、キー入力のたびに検証したり変換したりできます。非制御の入力は自分の値をDOMに持ち、必要なときにrefやフォームの FormData で読みます。ユーザーの入力にUIが反応するときは、制御された入力が標準的な選択です。

stateのリフトアップとはどういう意味ですか?

2つのコンポーネントが同じデータを必要とするとき、stateを最も近い共通の親に移し、それを変える関数と一緒にpropsとして下へ渡すことです。子は自分のコピーを持たなくなるので、食い違うことがありません。コンテキストやストアに手を伸ばす前の、「兄弟コンポーネントはどうやってstateを共有するか」への標準的な答えです。

フックのルールとは何ですか?

フックはコンポーネントかカスタムフックのトップレベルでだけ呼び、条件分岐、ループ、ネストした関数の中では決して呼ばないこと。そして普通のJavaScriptの関数ではなく、Reactの関数からだけ呼ぶことです。Reactは呼び出しの順番で各フックを識別するので、その順番はすべてのレンダリングで同じでなければなりません。eslint-plugin-react-hooks パッケージが両方のルールを強制します。

フラグメントとは何ですか?

フラグメントは、DOMノードを追加せずに複数の要素をまとめます:<>...</>、リストの中でkeyが必要なら <Fragment key={id}> です。コンポーネントはルートを1つ返す必要があり、フラグメントを使えばそのルートを複数の兄弟にできます。余分な div がマークアップを壊すテーブルやflexのレイアウトの中で重要になります。

中級の質問

useEffectの依存配列はどう動きますか?

Reactは、レンダリングがコミットされた後(通常はブラウザが描画した後)にエフェクトを実行し、その後は配列の値が変わった(Object.is で比較)レンダリングの後に再び実行します。配列がなければレンダリングのたびに、[] なら最初のレンダリングの後に1回だけ実行されます。エフェクトが読むコンポーネント内のすべての値を配列に入れるべきで、どれかが欠けていればlinterが教えてくれます。

エフェクトのクリーンアップはいつ実行されますか?

エフェクトから返した関数は、新しい依存値でエフェクトが再実行される前と、コンポーネントがアンマウントされるときに1回実行されます。エフェクトがセットアップしたものを元に戻すのはここです。タイマーを解除し、リスナーを削除し、接続を閉じます。

「Switch room」をクリックしてコンソールを見てください。まず古いルームのクリーンアップが実行され、次にエフェクトが新しいルームに接続します。「Hide chat」ではクリーンアップだけが実行されます。開発中は、クリーンアップが動くことを確かめるために、StrictModeがマウント時に接続、切断、接続を1回余分に行います。プレビューは本番ビルドのように動くので、ここでは本当の順番だけが見えます。

setIntervalの中でstateが古く見えるのはなぜですか?

intervalのコールバックは、それを作ったレンダリングの count を閉じ込めたクロージャです。依存配列が空だとエフェクトは二度と実行されないので、コールバックはその最初の count をずっと読み続けます。

broken は1になって止まります。毎回のtickが 0 + 1 を計算するからです。fixed は更新関数が最新の値を受け取るので、数え続けます。broken を依存配列に追加しても動きますが、毎秒intervalを解除して作り直すコストがかかります。

バッチ処理とは何ですか?

Reactは複数のstateの更新を1回のレンダリングにまとめます。React 18からは、イベントハンドラ、タイムアウト、Promise、ネイティブのイベントリスナーなど、どこでもこれが適用されます。1つの更新の直後にDOMを更新する必要があるなら、react-dom の flushSync でそれを外せますが、必要になることはまれです。

クリックすると、3回のレンダリングではなく render 1 1 1 が1回だけ記録されます。React 18より前は、タイムアウトの中の3つのstateの更新は3回のレンダリングを意味していました。

これは何をログに出しますか?(エフェクトの順番)

コードを読み、コンソールを見る前に、出力される4行を順番に書き出してください。

レンダリングは上から下へ進みます:render App、次に render Child。エフェクトはツリー全体がコミットされた後、下から上へ実行されます:effect Child、次に effect App。親のエフェクトは、子のエフェクトがすでに実行されたことを前提にできます。

useMemoとuseCallbackの違いは何ですか?

useMemo(() => compute(a, b), [a, b]) は計算の結果をキャッシュします。useCallback(fn, [deps]) は関数そのものをキャッシュするので、useMemo(() => fn, [deps]) と同じです。どちらも、memo の子やほかのフックの依存配列のように、下流で何かが参照で比べるときにだけ役立ちます。React Compiler 1.0(2025年10月)以降は、ビルドの工程でこのメモ化を自動で追加できますが、手書きのメモ化も今も動き、ほとんどのコードベースで目にするでしょう。

React.memoは何をするもので、なぜuseCallbackがよく必要になるのですか?

memo(Component) は、propsが前回と同じなら(Object.is で1つずつ比較)、そのコンポーネントの再レンダリングを飛ばします。親でインラインに書いた関数はレンダリングのたびに新しい関数になるので、変わったpropと見なされ、memo を打ち消します。useCallback はレンダリング間で同じ関数を保ちます。

コンソールをクリアして「Re-render parent」をクリックしてください。表示されるのは render inline handler だけです。もう一方の子は同じ関数を受け取ったので飛ばされました。Button の周りの memo(...) を取り除くと、どちらもクリックのたびにログを出します。useCallback だけでは何もしないことがわかります。

コンテキストとは何で、どんなコストがありますか?

コンテキストは、propsを通さずに、プロバイダーの下のすべてのコンポーネントに値を渡します。createContext を作り、React 19では <ThemeContext value={theme}>(または <ThemeContext.Provider>)で提供し、useContext(ThemeContext) で読みます。コストは、値が変わるとそのコンテキストを読むすべてのコンポーネントが再レンダリングされ、memo でもそれを止められないことです。無関係な値を別々のコンテキストに分ける、オブジェクトの値を useMemo で包む、頻繁に変わるstateをアプリ全体のコンテキストに入れない、といった方法で安く保ちます。useContextを参照してください。

refとは何で、stateの代わりにいつ使いますか?

useRef(initial) は、current プロパティがレンダリングをまたいで残るオブジェクトを返し、それを変えてもレンダリングは起きません。画面が依存しない値(タイマーのID、前回の値、フラグ)やDOMへのアクセス(<input ref={inputRef}> から inputRef.current.focus())にはrefを使います。画面に表示する値なら、stateに置くべきです。

仮想DOMとは何で、リコンシリエーションとは何ですか?

レンダリングのたびにReactは要素の新しいツリーを作り、前回のものと比べます。この比較がリコンシリエーションです。要素の型が変わると(div が section に、ProfileA が ProfileB になると)、Reactはそのサブツリーとそのstateを破棄して新しく作ります。型が同じなら、DOMノードを保って変わったpropsを更新し、リストの子を対応させるにはkeyを使います。「仮想DOM」はそのメモリー上のツリーのよく知られた呼び名で、速さはツリー自体が速いことではなく、変わったものだけに触れることから来ています。

カスタムフックとは何ですか?

名前が use で始まり、ほかのフックを呼ぶ関数で、stateを持つロジックをコンポーネント間で再利用できます:useOnlineStatus、useLocalStorage、useDebouncedValue。それを呼ぶ各コンポーネントは独立したstateを持ちます。カスタムフックが共有するのはロジックで、データではありません。カスタムフックを参照してください。

StrictModeは何をするものですか?

<StrictMode> は、開発中だけ追加のチェックを有効にします。純粋でないレンダリングをあらわにするためにコンポーネントを2回レンダリングし、クリーンアップの欠落をあらわにするためにマウント時にエフェクトのセットアップ、クリーンアップ、セットアップを1回行い、refコールバックにも同じことをし、非推奨のAPIについて警告します。本番ビルドではどれも起きないので、開発中にコンソールのログが2回出るのは想定どおりで、バグではありません。

上級の質問

これは何をログに出しますか?(レンダリング、レイアウトエフェクト、エフェクト)

もう1つ予想してください。3つのログが、レンダリングの3つの異なる瞬間に出ます。

useEffect が先に書かれていても、順番はレンダリング、useLayoutEffect、useEffect です。レイアウトエフェクトは、ReactがDOMを更新した後、ブラウザが描画する前に実行されるので、要素を測るのに向いています。通常のエフェクトはその後、たいていは描画の後に実行され、こちらを標準にすべきです。

エラーバウンダリとは何ですか?

子のレンダリング中に投げられたエラーを捕まえ、アプリ全体をアンマウントする代わりにフォールバックを表示するクラスコンポーネントです。static getDerivedStateFromError(フォールバックに切り替えるため)と、たいていは componentDidCatch(ログのため)が必要です。今もフックでの同等品はないので、関数コンポーネントのコードベースでは、クラスを1つ書くか、react-error-boundary パッケージを使います。

「Break it」をクリックしてください。フォールバックが置き換えるのはバウンダリの子だけで、ボタンは残り、Reactは捕まえたエラーをコンソールにも報告します。バウンダリは、イベントハンドラ、setTimeout のような非同期のコード、バウンダリ自身のエラーは捕まえません。それらには try/catch を使ってstateをセットします。

ポータルとは何ですか?

react-dom の createPortal(children, domNode) は、Reactのツリーでは同じ場所に保ったまま、子を別のDOMノード(たいていは document.body)に描画します。モーダル、ツールチップ、トーストは、これを使って親の overflow: hidden や重ね合わせコンテキストから抜け出します。コンテキストはポータルを通しても動き、イベントはReactのツリーを通って伝わるので、DOMノードが別の場所にあっても、ポータルの中のクリックは親の onClick に届きます。ポータルを参照してください。

Suspenseとlazyとは何ですか?

lazy(() => import('./Chart')) は、コンポーネントのコードを最初に描画されたときに読み込み、バンドルを分割します。<Suspense fallback={<Spinner />}> は、中の何か(lazyのコード、または use(promise) やSuspense対応のフレームワークで読むデータ)がまだ読み込み中の間、フォールバックを表示します。境界の配置によって、ページのどの部分が一緒に待つかが決まります。

import { lazy, Suspense } from 'react';

const Chart = lazy(() => import('./Chart.jsx'));

export default function Dashboard() {
    return (
        <Suspense fallback={<p>Loading chart...</p>}>
            <Chart />
        </Suspense>
    );
}

useTransitionは何をするものですか?

stateの更新を緊急でないものとして扱います。const [isPending, startTransition] = useTransition() と startTransition(() => setTab('posts')) を使うと、Reactは現在の画面の応答性を保ち、キー入力のようなもっと緊急な更新が来たら遅いレンダリングを中断できます。isPending はヒントを表示すべきことを教えてくれます。React 19では渡す関数をasyncにでき、Reactはそれをアクションと呼びます。テキスト入力そのものの値の更新には使わないでください。その更新は緊急でなければなりません。

React Server Componentsとは何ですか?

サーバーで(またはビルド時に)だけ実行され、コードではなく描画した結果をブラウザに送るコンポーネントです。async にでき、データベースやファイルシステムを直接読めますが、state、エフェクト、ブラウザのAPIは使えません。'use client' と書いたファイルは、対話的なコンポーネントが置かれるクライアントの境界を始め、'use server' はクライアントのコードから呼べるサーバー関数を示します。App Routerを使うNext.jsのようなフレームワークを通じて使い、素のViteアプリにはそれらを実行するサーバーがありません。

React 19で何が変わりましたか?

  • アクション:トランジションの中のasync関数で、保留中の状態を代わりに扱ってくれます。<form action={fn}> は、フォームの FormData を渡して fn を呼びます。
  • 新しいフック:フォームの結果と保留中の状態のための useActionState、楽観的なUIのための useOptimistic、そして react-dom の useFormStatus。
  • use:Promise(解決するまでサスペンドします)やコンテキストを読み、ほかのフックと違って条件分岐の中でも呼べます。
  • propとしての ref:関数コンポーネントはほかのpropと同じように ref を受け取るので、新しいコードでは forwardRef は不要です。refコールバックはクリーンアップ関数を返せます。
  • プロバイダーとしての <Context>、そしてドキュメントのheadに引き上げられる <title> と <meta> タグ。
  • 削除されたもの:ReactDOM.render、文字列ref、レガシーコンテキスト、関数コンポーネントの defaultProps(引数のデフォルト値を使います)。
// React 19: ref is a normal prop
function TextInput({ ref, ...props }) {
    return <input ref={ref} {...props} />;
}

React 19のページに、それぞれの動く例があります。

Reactのコンポーネントはどうテストしますか?

React Testing Libraryと、VitestやJestのようなテストランナーを使います。コンポーネントを描画し、ユーザーと同じ方法(ロール、ラベル、テキスト)で要素を見つけ、user-event で操作し、内部のstateではなく目に見えるものを確認します。そうすれば、リファクタリングしてもテストが有効なままです。

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

test('counts clicks', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    await user.click(screen.getByRole('button', { name: /clicked 0 times/i }));
    expect(screen.getByRole('button')).toHaveTextContent('Clicked 1 times');
});

toHaveTextContent は @testing-library/jest-dom から来ています。ページをまたぐ一連の流れには、チームはその上にPlaywrightやCypressを加えます。

パフォーマンスの問題はどう見つけて直しますか?

まず測ってください。React DevToolsのProfilerタブを開き、遅い操作を記録して、フレームチャートを読みます。どのコンポーネントがレンダリングされ、それぞれどれだけ時間がかかったか、そして(「record why each component rendered」の設定で)なぜレンダリングされたかがわかります。典型的な直し方を試すべき順番に挙げると、stateを下へ移してそれに依存するコンポーネントを減らす、遅いサブツリーを children として渡す、重い部分を memo、useMemo、useCallback でメモ化する、長いリストを仮想化する、重い更新を useTransition や useDeferredValue で遅らせる、です。プロファイルは本番ビルドかプロファイリングビルドで行ってください。開発ビルドは遅く、レンダリングの時間を誇張します。

コンポーネントが純粋であるべきなのはなぜですか?

同じprops、state、コンテキストが与えられたら、コンポーネントは同じJSXを返し、レンダリング中に自分の外の何も変えるべきではありません。Reactはコンポーネントを複数回レンダリングしたり、順番を変えたり、レンダリングを捨てたりすることがある(トランジション、Suspense、StrictMode)ので、変数の変更やリクエストの送信のような本体の中の副作用は、予測できない回数実行されてしまいます。副作用はまずイベントハンドラに、イベントが原因でない場合はエフェクトに書きます。

エフェクトを使うべきでないのはどんなときですか?

値がレンダリング中に計算できるとき(フィルターしたリスト、フルネーム、合計)、作業がユーザーの操作によって起きるとき(イベントハンドラに書きます)、propの変化に応じてstateをリセットしたいとき(key を使います)です。エフェクトは、ネットワーク接続、ブラウザのAPI、サードパーティのウィジェットのような、Reactの外にあるものと同期するためのものです。stateをセットするエフェクトの連鎖は、余分なレンダリングとバグのよくある原因です。

よくある質問

Reactとは何ですか?

Reactは、コンポーネントからユーザーインターフェースを作るためのJavaScriptライブラリです。現在のデータに対して画面がどう見えるべきかを記述すると、データが変わったときにReactが実際のDOMを更新します。

stateとpropsの違いは何ですか?

propsは親がコンポーネントに渡す入力で、コンポーネントはそれを読み取り専用として扱います。stateはコンポーネントが持ち、セッターで時間とともに変えるデータで、変えるとReactがコンポーネントを再レンダリングします。

Reactのフックとは何ですか?

フックは名前が use で始まる関数で、関数コンポーネントがReactの機能を使えるようにします。stateには useState、副作用には useEffect、コンテキストには useContext、レンダリングを起こさない値には useRef などがあります。

仮想DOMとは何ですか?

Reactがメモリー上に持つUIの記述の呼び名です。レンダリングのたびにReactは新しい記述を作り、前回のものと比べて、違いだけを実際のDOMに適用します。この比較をリコンシリエーションと呼びます。

Reactのリストでkeyが重要なのはなぜですか?

keyは、レンダリング間でリストのどの項目がどれかをReactに伝えます。安定したkey(idなど)があれば、項目が追加、削除、並べ替えされても、Reactは各項目のstateとDOMノードを正しいデータに結びつけたまま保てます。配列のindexを使うと、順番が変わったときにこれが壊れます。

JSXとは何ですか?

JSXは、JavaScriptの中にHTMLに似たマークアップを書けるようにする構文拡張です。コンパイラが各タグをReact要素を作る関数呼び出しに変えるので、<h1>Hi</h1> は h1 を表す普通のオブジェクトになります。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める