0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React開発で生成AIを相棒にするためのプロンプト設計 〜現場で試行錯誤して辿り着いた5つのコツ〜

0
Posted at

深夜のデプロイ直前、コンポーネントのリファクタリングをAIに任せたら、見慣れないエラーがコンソールを埋め尽くした経験はないだろうか。「これ、直せって言ったのになぜ壊れるんだ」と画面を見つめながら、プロンプトを微調整しては再実行を繰り返す。そんな夜を何度か越えてきたからこそ言えるのは、生成AIを「魔法の杖」ではなく「クセの強い優秀な新人」として扱うマインドセットが、React開発の現場では何より役立つということだ。

1. コンテキストは「ファイル単位」ではなく「機能単位」で渡す

最初の頃、私は「このファイルをリファクタして」と単一のコンポーネントファイルだけを貼り付けていた。するとAIは、そのコンポーネント内部のロジックは綺麗に整理してくれるものの、親コンポーネントから渡されるpropsの型定義や、カスタムフック側のインターフェースとの整合性を取りこぼす。結果として、型エラーが連鎖し、修正コストが跳ね上がった。

なぜそうなるのか。Reactのコンポーネントは孤立して動かないからだ。あるボタンコンポーネントを修正するなら、それを呼び出している親の状態管理、さらにその親が使っているContextやReduxのslice、場合によってはAPIのレスポンス型まで、関連する「機能の塊」を一緒に見せる必要がある。

具体的にはどうするか。私の場合、VS Codeの拡張機能やCLIツールを使って、対象のコンポーネントから遡れる依存関係(親コンポーネント、カスタムフック、型定義ファイル、関連するテストファイル)をまとめて一つのマークダウンブロックとして出力し、プロンプトの冒頭に「以下は『ユーザー登録フロー』に関わるファイル群です」という一文とともに貼り付けるようにしている。


✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

https://www.youtube.com/@tech-trend-zunda-metan/featured

最新ツール・トレンド情報をずんだもん×めたんが解説するYouTubeチャンネルを運営しています!
いいね、チャンネル登録してもらえると嬉しいです🙇‍♂️

---

ハジメル.dev: https://hajimeru-dev.vercel.app/

「ひとりで続けるのは難しい」「何から学べばいいか分からない」という方向けに、
プログラミングのマンツーマンレッスンサービス「ハジメル.dev」も運営しています。
未経験OK・オンライン完結・月額制/違約金なしなので、気軽に無料相談してみてください🙇‍♂️


---

海外テックニュースを追いたいけど、英語や情報量の多さで大変…という方向けに、
Hacker News の話題を日本語でサクッと追える「HackerNews 日本語まとめ & AI要約」
を個人開発しました!
技術トレンド収集に使ってもらえると嬉しいです🔥🙇‍♂️
→ HackerNews 日本語まとめ & AI要約: https://hn-matome-2ht.pages.dev/



---

https://unityroom.com/games/nyampire_survivors

「ニャンパイアサバイバー」というヴァンパイアサバイバーリスペクトのゲームを作成しました!
もしよろしければ遊んで頂けると嬉しいです😭

---

習い事教室の先生向けに、SNS 投稿・生徒募集・保護者通知の文章を AI で生成する Web サービス「おしらせAI」を個人開発しました。Next.js + Supabase + LLM で構成しており、無料で月 10 回まで試用できます。よければ触ってみてください。

→ おしらせAI: https://oshirase-ai.vercel.app/

---

言いたいことがうまく伝わらない…という方向けに、会話の言い方を添削する「伝え方ラボ」を開発中です。
場面を選んで自分の言葉で返すと、何が伝わっていないかの指摘と、そのまま使える言い換えが返ってきます。
ChatGPT に相談すると褒めから入って直すべきところが残りがちなので、指摘側に振り切りました。
現在は公開時にお知らせするメール登録のみ受付中です🙇‍♂️

→ 伝え方ラボ: https://tsutaekata-lab.pages.dev/

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

# 対象機能: ユーザー登録フロー

## src/features/signup/components/SignupForm.tsx
```tsx
// ... コード ...

src/features/signup/hooks/useSignupForm.ts

// ... コード ...

src/features/signup/types.ts

// ... 型定義 ...

こうするだけで、「propsの型を合わせて」「useSignupFormの戻り値のインターフェースを変えないで」といった文脈理解がAI側で働き、破壊的変更を含まない提案が返ってくる確率が劇的に上がった。ファイル単位の断片ではなく、機能単位のストーリーとして渡す。これが現場で最初に身につけたい癖だ。

## 2. 「やってほしいこと」より「やってほしくないこと」を先に書く

AIにコード生成を依頼するとき、ついつい「この機能を実装して」「ここを直して」という肯定的な指示だけを書きがちだ。しかし、Reactの現場には暗黙のルールやチームごとのコーディング規約、パフォーマンス上のアンチパターンが山ほど存在する。「useEffectの中でsetStateを連打しないで」「クラスコンポーネント風のライフサイクルをhooksで再現しないで」「インライン関数をJSX内で定義してメモ化を無効にしないで」——こうした「やってほしくないこと」をネガティブ制約として明示すると、レビュー工数が激減する。

自分の失敗談を一つ。ある時、データフェッチのロジックをカスタムフックに切り出すよう依頼した。AIは見事に`useEffect`と`useState`を使ったフックを書いてくれたが、依存配列に`dispatch`やらコールバック関数やらを全部入れてしまい、無限ループ寸前のコードを吐き出した。もちろん動くには動くが、再レンダリングが走りまくる。

それ以来、プロンプトの冒頭か末尾に必ず以下のようなブロックを入れるようにしている。

> ## 制約事項(必ず守ること)
> - `useEffect` の依存配列にはプリミティブな値、または `useCallback`/`useMemo` でメモ化された関数のみを指定すること。インライン関数を直接書かないこと。
> - `React.memo` で包まれたコンポーネントに対して、propsとして新しいオブジェクトや関数を毎回生成して渡さないこと。
> - 状態更新関数(setState)をループ内やイベントハンドラ内で同期的に複数回呼ばないこと。関数型アップデート形式(`setCount(c => c + 1)`)を活用すること。
> - `any` 型を使わないこと。不明な型は `unknown` から絞り込むか、Generics で外部から受け取ること。
> - コメントアウトされたコードや、未使用のimportを残さないこと。

これはチームのESLintルールや、過去のコードレビューで指摘されがちな項目を言語化したものだ。AIは「こうしろ」という肯定指示より、「これはダメ」という否定制約の方が忠実に守る傾向がある。肯定指示は「解釈の余地」が生まれるが、否定制約は「ガードレール」として機能しやすいからだ。もちろん、肯定的な指示(例:「TanStack Queryのパターンで書いて」「Zodでバリデーションスキーマを定義して」)とセットで使うのが前提だが、この「禁止事項リスト」をテンプレート化しておくだけで、後で「あ、これESLintで怒られるやつだ」と気づく手戻りが減る。

## 3. 型定義を「最初に」渡し、「型から実装へ」の順序を守る

ReactとTypeScriptの組み合わせでは、型こそが設計図だ。しかし、最初の頃は「この機能を実装して」と言うと、AIが勝手に`interface Props { ... }`をその場で適当に生成し、実装もそれに合わせて書き始める。後から「いや、このprops名はチームの命名規則と違う」「この型、APIのレスポンスと合わない」となって手戻りが発生する。

あるプロジェクトで、バックエンドのOpenAPIスキーマからフロントエンドの型を自動生成するパイプラインが整っていた。それを知らずにAIに「型も含めて書いて」と言った結果、バックエンドの定義と微妙にズレた型がフロントに入り込み、ランタイムでパースエラーになったことがある。

それ以来、プロンプトの最初のステップとして、必ず「既存の型定義を貼り付ける」か「型定義だけを先に生成させる」フェーズを設けるようにした。

**ステップ1:型定義の確認・生成**
> 以下のAPIレスポンス例と、既存の共通型定義を元に、`UserProfile` コンポーネントが受け取る `Props` の型定義だけを書いてください。実装はまだ書かないでください。命名規則は `PascalCase` で、Optionalなプロパティには必ず `?` をつけ、デフォルト値が必要な場合は JSDoc の `@default` タグで示してください。

**ステップ2:実装**
> 承認された型定義を使って、`UserProfile` コンポーネントを実装してください。以下の制約を守ってください。
> - 表示ロジックとデータ取得ロジックを分離し、データ取得は `useUserProfile` というカスタムフックに切り出すこと。
> - ローディング・エラー・空状態のUIをそれぞれ別コンポーネントとして分割すること(`UserProfileSkeleton`, `UserProfileError`, `UserProfileEmpty`)。
> - アクセシビリティ属性(`aria-label`, `role` など)を適切に付与すること。

このように「型→実装」の二段階に分け、ステップ1の出力を人間が目視確認(あるいは別のAIにレビューさせる)してからステップ2に進む。これだけで「実装は動くが型が嘘をついている」という最悪の事態を防げる。型定義は契約書だ。契約書なしで工事を始めさせない、というのが鉄則だ。

## 4. テストコードを「仕様書」として使い、リグレッションを防ぐ

「動くコード」を書かせるのはもはや簡単だ。だが、「仕様変更に強いコード」を書かせるには、テストをプロンプトに組み込むのが一番手っ取り早い。特にReact Testing Library(RTL)を使ったコンポーネントテストは、実装詳細ではなく「ユーザーから見た振る舞い」を検証するため、AIにとっても「正解の基準」が明確になる。

私がよくやるのは、実装を依頼する前に「期待する振る舞い」をテストコードとして書かせ(あるいは自分で雛形を書き)、それに合う実装を生成させる「テスト駆動プロンプト」だ。

> 以下の仕様を満たす `LoginForm` コンポーネントのテストコードを React Testing Library で書いてください。実装はまだ書かないでください。
>
> **仕様**
> - メールアドレスとパスワードの入力欄がある
> - 両方が埋まっていない状態で送信ボタンを押すと、バリデーションエラーが表示され、送信処理は走らない
> - 正しい形式で入力し送信すると、ローディング状態になり、送信中はボタンが無効化される
> - 送信成功時は `onSuccess` コールバックが呼ばれ、失敗時はエラーメッセージが表示される

AIが吐き出したテストコードを見て、「あ、パスワードの表示/非表示トグルのテストが抜けている」「アクセシビリティのテスト(ラベルとインプットの紐付け)が足りない」と気づける。人間がテストをレビューし、OKなら「このテストをパスする実装を書いて」と依頼する。実装が完成したら、そのままテストを実行させる(あるいはCIで回す)。これで「仕様通りに動くこと」が担保される。

さらに、既存のコンポーネントをリファクタリングする際も、「現在のテストコードを貼り付け、これを壊さずにリファクタして」と伝える。AIはテストをグリーンに保つ方向でコードを書き換えようとするため、意図しない挙動変更(リグレッション)が混入しにくい。テストコードは「仕様のエグゼキュータブルなドキュメント」であり、AIへの最強の制約条件でもあるのだ。

## 5. 「なぜそう書いたか」を会話ログに残し、ナレッジとして蓄積する

これが一番地味だが、長期的に効いてくる習慣だ。AIとのやり取りはチャット履歴として残るが、そのままでは「あの時どうしてこうなったっけ?」が後で追えない。特にReactでは、「なぜ `useMemo` を使ったのか」「なぜ Context を分けたのか」「なぜこのコンポーネントだけ `forwardRef` が必要だったのか」といった設計判断の理由が、コードだけ見ても分からないことが多い。

私の運用では、AIがある程度まとまったコードを出力したら、必ず以下のような「設計メモ」を追加で生成させ、会話ログの最後や、プロジェクトのドキュメント(NotionやGitHub Wiki、あるいはリポジトリ内の `docs/ai-decisions/` など)にコピペして保存している。

> ## 設計判断の記録: UserDashboard のリファクタリング (2024-XX-XX)
> **背景**: 親コンポーネント `UserDashboard` が巨大化し、タブ切り替えごとに無関係な状態まで再レンダリングされていた。
> **決定**: タブごとのコンテンツを `React.lazy` + `Suspense` で遅延ロードし、それぞれ独立した Context (`UserProfileContext`, `UserSettingsContext`, `UserBillingContext`) に分離した。
> **理由**:
> - タブ切り替え時に不要なコンテキストの更新を購読しないようにするため。
> - バンドルサイズ削減のため、初期表示に不要なタブのコードを遅延させるため。
> - 各ドメインの状態管理を疎結合に保つため。
> **トレードオフ**: Context 分離により、タブ間でデータを共有する場合は親経由で渡す必要が出た(Props Drilling の再発)。今回は共有データが少ないため許容した。
> **AIへの指示内容**: 「Context分離と遅延ロードを同時にやって」と一括依頼したら、Suspenseの境界が間違っていた。段階的に「まずContext分離」「次に遅延ロード」と分けて指示したらうまくいった。

このように「背景・決定・理由・トレードオフ・AIへの指示のコツ」をセットで残す。半年後、同じようなリファクタリングをする時に、このメモを見返せば「あ、Context分けるときはSuspense境界に気をつけろって自分で書いてある」と即座に思い出せる。AIとの対話履歴を「個人のナレッジベース」として資産化する。これが、駆け出しから一歩抜け出すための、地味だが最強の習慣だと思う。

## まとめ

生成AIをReact開発の現場で実戦投入するために必要なのは、プロンプトテクニックの暗記ではなく、「AIにどこまで任せ、どこを人間が握るか」という責任分界線の設計だ。コンテキストを機能単位で渡し、否定制約でガードレールを引き、型定義を契約書として先に固め、テストを仕様書として機能させ、判断の理由を言語化して資産にする。この5つのサイクルを回せるようになってから、AIは「手間のかかる新人」から「信頼できるペアプロ相手」に変わった。完璧なプロンプトなど存在しない。だが、「次はこうしてみよう」と試行錯誤し続けるそのプロセスこそが、エンジニアとしての設計力を底上げしてくれる。今夜、一つでもいいから試してみてほしい。きっと、明日のコードレビューが少しだけ楽になっているはずだ。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?