Powered By qiita.com
問題1
以下は、非同期処理の状態管理を行うコードです。非同期処理をしたあとの結果でステート更新ができるフックを使用しています。
const initialPosts: Post[] = []; const [posts, getPosts, isPending] = ■■■■■■■■■■■■( async (currentPosts: Post[], payload: void) => { const response = await fetch('https://api.example.com/posts'); const newPosts: Post[] = await response.json(); return newPosts; }, initialPosts ); const handleClick = () => { getPosts(); }; return ( <div> <button onClick={handleClick} disabled={isPending}> {isPending ? '読み込み中...' : '投稿を取得'} </button> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> );
問1の答えと解説を見る
正解: useActionState
解説: useActionStateは、非同期処理とステート管理を組み合わせたフックで、useTransition + useReducerのような機能を提供します。非同期処理の実行中はisPendingフラグでローディング状態を管理でき、処理完了後に自動的にステートが更新されます。第一引数には非同期処理を行う関数、第二引数には初期値を指定します。このフックにより、複雑な非同期ステート管理を簡潔に記述できます。
問題2
useEffectでデータ取得を行う際に発生する、親コンポーネントのデータフェッチが完了するまで子コンポーネントのデータフェッチが開始されず、全体の表示速度が遅くなる問題を■■■■■■■■■■■問題と呼びます。
問2の答えと解説を見る
正解: ウォーターフォール
解説: ウォーターフォール問題は、コンポーネントが階層的にデータを取得する際、親から子へと順次データフェッチが行われる現象です。本来並列で実行できるAPI呼び出しが順次実行されるため、全体的なパフォーマンスが低下します。この問題を回避するには、React QueryやSWR、TanStack Queryなどのデータフェッチライブラリを使用するか、データ取得を上位コンポーネントでまとめて行うアプローチが有効です。
問題3
以下は、DOM要素への直接アクセスを行うコードです。適切なフック名を埋めてください。
const MyComponent = () => { const inputRef = ■■■■■■■■(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>フォーカスを当てる</button> </div> ); };
問3の答えと解説を見る
正解: useRef
解説: useRefは、DOM要素への参照や再レンダリングを引き起こさない値の保持に使用されるフックです。DOM要素にアクセスする場合、refオブジェクトのcurrentプロパティを通じて実際のDOM要素を取得できます。TypeScriptでは、null許容型で初期化し、アクセス時には?演算子やnull チェックを行うことで型安全性を保ちます。useStateと異なり、値の変更時に再レンダリングが発生しないため、パフォーマンス上のメリットもあります。
問題4
以下は、楽観的更新を実装するコードです。適切なフック名を埋めてください。
function LikeButton() { const [likes, setLikes] = useState(0); const [optimisticLikes, addOptimisticLike] = ■■■■■■■■■■■■( likes, (state: number) => state + 1 ); const handleClick = async () => { addOptimisticLike(); try { await fetch('/api/like', { method: 'POST' }); setLikes(likes + 1); } catch (error) { // エラー時は楽観的な更新が自動的にロールバックされる } }; return ( <button onClick={handleClick}> いいね!({optimisticLikes}) </button> ); }
問4の答えと解説を見る
正解: useOptimistic
解説: useOptimisticは、楽観的更新(Optimistic UI)を実装するためのフックです。ユーザーのアクション結果を即座に画面に反映し、バックグラウンドで実際の処理を行うことでUXを向上させます。第一引数には実際のステート、第二引数には楽観的更新時の変換関数を指定します。API呼び出しが失敗した場合、楽観的な更新は自動的にロールバックされ、元の状態に戻ります。いいね機能やフォーム送信などでよく使用されるパターンです。
問題5
以下のコードで、検索クエリの更新を遅延させるために使用すべきフック名を埋めてください。
import React, { useState } from 'react'; import ExpensiveList from './ExpensiveList'; function SearchableList() { const [searchQuery, setSearchQuery] = useState(''); const ■■■■■■■■■■■■ = ■■■■■■■■■■■■(searchQuery); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { setSearchQuery(e.target.value); }; return ( <div> <input type="text" value={searchQuery} onChange={handleSearch} placeholder="検索..." /> <ExpensiveList searchText={■■■■■■■■■■■■} /> </div> ); }
問5の答えと解説を見る
正解: deferredQuery, useDeferredValue, deferredQuery
解説: useDeferredValueは、値の更新を遅延させることができるフックです。useTransitionが関数自体を遅延するのに対し、useDeferredValueは値を遅延させます。主な用途は、サードパーティのコンポーネントなど、コードを直接制御できない場合です。入力値は即座に更新されますが、コストの高いコンポーネントには遅延された値が渡されるため、入力のレスポンシブ性を維持しながらパフォーマンスを向上させることができます。
問題6
以下は、複数の関連する状態を管理するコードです。適切なフック名を埋めてください。
interface FormState { name: string; email: string; isValid: boolean; } type FormAction = | { type: 'SET_NAME'; payload: string } | { type: 'SET_EMAIL'; payload: string } | { type: 'VALIDATE' }; function formReducer(state: FormState, action: FormAction): FormState { switch (action.type) { case 'SET_NAME': return { ...state, name: action.payload }; case 'SET_EMAIL': return { ...state, email: action.payload }; case 'VALIDATE': return { ...state, isValid: state.name !== '' && state.email !== '' }; default: return state; } } function Form() { const [formState, dispatch] = ■■■■■■■■■■■■(formReducer, { name: '', email: '', isValid: false }); return ( <form> <input value={formState.name} onChange={(e) => dispatch({ type: 'SET_NAME', payload: e.target.value })} /> <input value={formState.email} onChange={(e) => dispatch({ type: 'SET_EMAIL', payload: e.target.value })} /> </form> ); }
問6の答えと解説を見る
正解: useReducer
解説: useReducerは、複数の関連する状態や複雑な状態更新ロジックを扱う際に有効なフックです。useStateと比較して、アクション(action)という概念を使って状態の変更を一元管理できます。TypeScriptでは、StateとActionの型を適切に定義することで、型安全な状態管理が可能になります。複数の状態が密接に関連している場合や、条件分岐が多い更新ロジックがある場合にuseReducerを選択することで、コードの保守性と可読性が向上します。
問題7
以下は、優先度の低い状態更新を後回しにするコードです。適切なフック名を埋めてください。
function Search() { const [searchTerm, setSearchTerm] = useState(''); const [results, setResults] = useState<SearchResult[]>([]); const [isPending, ■■■■■■■■■■■■] = ■■■■■■■■■■■■(); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { setSearchTerm(e.target.value); ■■■■■■■■■■■■(() => { // 大量のデータを検索する処理(時間がかかる) const filtered = performExpensiveSearch(e.target.value); setResults(filtered); }); }; return ( <> <input value={searchTerm} onChange={handleSearch} /> {isPending && <div>更新中...</div>} <SearchResults results={results} /> </> ); }
問7の答えと解説を見る
正解: startTransition, useTransition, startTransition
解説: useTransitionは、優先度の低い状態更新を後回しにできるフックです。ユーザーの入力などの高優先度の更新を妨げることなく、重い処理を実行できます。isPendingフラグを使用して処理中の状態を表示し、startTransition関数で優先度の低い更新をラップします。検索機能、ページネーション、タブ切り替えなど、即座の反応が不要な処理に適用することで、ユーザー体験を大幅に向上させることができます。React 18の concurrent features の一部として導入されました。
問題8
useEffectでデータ取得を行う際に発生する、複数のリクエストが同時に発生した場合に古いリクエストの結果が新しいリクエストの結果を上書きしてしまう問題を■■■■■■■■■■■■■問題と呼びます。
問8の答えと解説を見る
正解: レースコンディション
解説: レースコンディション問題は、非同期処理において複数のリクエストが競合状態になる問題です。例えば検索機能で「りんご」を検索後すぐに「りんごジュース」を検索した場合、「りんご」のAPI応答が遅くて後から到着すると、新しい検索結果を古い結果で上書きしてしまいます。この問題を避けるには、useEffectのクリーンアップ関数でAbortControllerを使用するか、React QueryやSWRなどのライブラリを使用することが推奨されます。これらのライブラリは自動的にリクエストの競合を管理してくれます。
問題9
以下は、一意のIDを生成するコードです。アクセシビリティとSSRの安全性のために使用されるフック名を埋めてください。
function Form() { const id = ■■■■■■■(); return ( <form> <div> <label htmlFor={`${id}-name`}>名前:</label> <input id={`${id}-name`} type="text" /> </div> <div> <label htmlFor={`${id}-email`}>メール:</label> <input id={`${id}-email`} type="email" /> </div> </form> ); }
問9の答えと解説を見る
正解: useId
解説: useIdは、一意のIDを生成するためのフックです。主にアクセシビリティの向上(ラベルとフォーム要素の紐付け)とSSR(サーバーサイドレンダリング)の安全性のために使用されます。ランダムなIDを使用するとサーバーとクライアントで異なるIDが生成され、ハイドレーション時に不整合が発生する可能性がありますが、useIdは一貫したIDを生成するため、この問題を回避できます。複数のID が必要な場合は、基本IDに接尾辞を付けて使用します。
問題10
以下は、DOMの変更を同期的に処理するコードです。useEffectではちらつきが発生してしまうため、適切なフック名を埋めてください。
function NoFlickerComponent() { const [position, setPosition] = useState({ x: 0, y: 0 }); ■■■■■■■■■■■■(() => { setPosition({ x: window.innerWidth - 100, y: window.innerHeight - 100 }); }, []); return ( <div style={{ position: 'absolute', left: `${position.x}px`, top: `${position.y}px` }} > Hello World </div> ); }
問10の答えと解説を見る
正解: useLayoutEffect
解説: useLayoutEffectは、DOMの変更を同期的に処理するためのフックです。useEffectと異なり、ブラウザが画面を描画する前に実行されるため、視覚的なちらつき(flicker)を防ぐことができます。DOM要素のサイズや位置を測定・変更する処理、スクロール位置の調整、初期レンダリング時のレイアウト計算などに使用されます。ただし、同期実行のためパフォーマンスに影響を与える可能性があるので、必要な場合のみ使用し、通常はuseEffectを優先すべきです。
まとめ
これらのReact Hooksは、モダンなReactアプリケーション開発において重要な役割を果たします。特に以下の点に注意して実装することが重要です:
- パフォーマンス: useMemo、useCallback、useTransition を適切に使用してレンダリング最適化を図る
- データ取得: useEffectでの直接的なデータ取得は避け、専用ライブラリの使用を検討する
- 型安全性: TypeScriptの型システムを活用し、各フックに適切な型定義を行う
- ユーザー体験: useOptimistic、useTransitionを使用して応答性の高いUIを実現する