narrow-react-prop-types について
React コンポーネントのプロパティ型を、実際のコードパスで使われる状態に合わせて厳密化するスキルです。プロダクションの呼び出し箇所を契約の根拠とし、関連コンポーネント、テスト、Storybook のストーリー、検証も調整します。
主な機能
- 01実際の呼び出し箇所から契約を導出
- 02プロパティを必須・任意・削除に分類
- 03公開型と子コンポーネント型を厳密化
- 04未対応状態向けのフォールバックを削除
- 05ストーリーとテストを合わせて更新
活用シーン
- 01Storybook やテスト用に広げたプロパティを絞る
- 02常時操作可能な UI の任意ハンドラーを削除
- 03共有コンポーネントの型を実運用に合わせる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ humanlayer/ skills --skill narrow-react-prop-typesインストール後、Claude Code で/narrow-react-prop-typesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/agent-narrow-component-props.ymlnarrow-component-props-memory.mdresponse-template.md詳細
React の Props 型を絞り込む
React コンポーネントの Props が、Storybook、モック、テスト、デモ用に広げられ、実際のアプリケーションでは発生しない状態まで表現するようになっている場合に、このスキルを使用してください。
目標は、コンポーネントの型で実際のライブコードパスの契約を表し、契約を弱めるのではなく、Storybook/テスト/モック側でその契約に適合させることです。
基本要件
- 型を変更する前に、テスト用でも Storybook 用でもない実際の呼び出し箇所を確認してください。
- Props の契約を定める際は、ライブコードパスを信頼できる基準としてください。
- Storybook、テスト、モックデータを扱いやすくするためだけに、Props を任意のままにしないでください。
- Storybook 用でもテスト用でもない呼び出し箇所で Props が指定されず、かつ指定しない正当な理由がある場合に限り、Props を任意のままにしてください。
- 型によって、Storybook 用でもテスト用でもない呼び出し箇所で確認されていない状態を表現できるようにしてはいけません。
- コードをできるだけシンプルにできるよう、型は可能な限り厳密にしてください。
- 可能な限り、既存のライブコードパスの値や API から型を導出し、抽出してください。
ワークフロー
このスキルを CodeLayer 経由で実行する、定期的な GitHub Actions ワークフローの例については、references/agent-narrow-component-props.yml を参照してください。エージェントのメモリファイルの例は references/narrow-component-props-memory.md です。CI エージェントの応答形式については、references/response-template.md を参照してください。
1. 対象のコンポーネントを特定する
次のような特徴があるコンポーネントを探してください。
- 多くのフィールドが任意になっている、大きな Props インターフェース。
onSelect?.(...)やonArchive?.(...)のような、任意のコールバック呼び出し。items ?? []、count ?? 0、またはライブコードでは常に値が渡される可能性が高い箇所でのhandler && ...のようなフォールバック処理。- コールバックが任意なのに、常に表示される UI 操作要素。
defaultFoo、別形式のハンドラー、ライブコードでは使われていない表示切り替えなど、デモ向けに見える Props。
Storybook またはテストだけを根拠に対象を選ばないでください。Storybook/テストは型が広げられていることを裏付ける証拠としてのみ使用し、その状態が実際に存在する根拠としては使用しないでください。
2. すべてのライブ使用箇所を確認する
コンポーネント、エクスポートされた Props 型、共有される子プリミティブのすべてのインポート箇所と使用箇所を検索してください。
呼び出し箇所を、ライブコードパスか補助コードかに分類してください。
- ライブコードパス:アプリのルート、接続済みのコンポーネント、プロバイダー、フック、本番用パッケージのエクスポート、およびそれらのパスで使用される共有コンポーネント。
- 補助コード:Storybook ストーリー、テストファイル、フィクスチャ、モック、デモ用ハーネス、ビジュアルのみの例。
コンポーネント API がサポートする内容を決めるのは、ライブコードパスだけにしてください。
3. ライブコードパスから実際の型を導出する
ライブの呼び出し箇所を読み、各 Prop を分類してください。
- 必須:テスト用でも Storybook 用でもないすべての呼び出し箇所で指定されている。
- 任意:テスト用でも Storybook 用でもない呼び出し箇所のうち、少なくとも 1 つで指定されず、その省略が意味のある実行時状態である。
- 削除:テスト用でも Storybook 用でもない呼び出し箇所では使用されていない。
null 許容と任意は異なります。ライブコードで Prop が常に渡されるものの、その値が空になることがある場合は、focusedItem?: FocusedItem | null よりも focusedItem: FocusedItem | null のような、必須かつ null 許容の Prop を優先してください。
4. 公開 Props 型を厳密にする
エクスポートされた Props 型を更新し、ライブコードパスで確認される状態だけを表すようにしてください。
型の許容範囲が広く、任意の項目が多いほど、コンポーネントが考慮しなければならない状態も増えます。任意の Prop をひとつ追加するたびに、コンポーネントは別の分岐を処理し、テストし、正しく保つ必要があります。あり得ない状態を許さない厳密な型を優先し、広い型によって防御的なレンダリングロジックを要求するのは避けてください。
コンポーネントが操作可能な UI 要素を常に表示する場合は、それを機能させるハンドラーを必須にしてください。onRename?.(...) を呼び出すメニュー項目が表示されるだけのような、操作できない状態を許可しないでください。
5. 可能な場合は型を導出・抽出する
型を手動で再記述するのではなく、可能な限り実際の API から型を導出してください。
- 関数の引数型には
Parameters<typeof fn>[0]を使用します。 - 戻り値の型には
ReturnType<typeof fn>を使用します。 - Union を実際のバリアントに絞り込むには
Extract<Union, Shape>を使用します。 - React の状態セッターには、
(value: T) => voidのように近似するのではなく、React.Dispatch<React.SetStateAction<T>>を使用します。
型推論によって意図した状態の形が広がったり不明瞭になったりする場合は、状態の型パラメーターを明示してください。
const [dialogState, setDialogState] = useState<DialogState>({
id: null,
isOpen: false,
})
useState(...) の暗黙的な型推論に頼るのは避けてください。広すぎる nullable なオブジェクト型、文字列リテラル型の拡大、または後から手書きの近似が必要になるコールバック型が生成される場合があります。
6. 内部の子コンポーネントの props も厳密にする
エクスポートされたコンポーネントだけで終わらせず、広すぎる props を子プリミティブに渡している場合は、それらも見直してください。
親の props が広すぎることだけを理由に、行/メニュー/ボタンの子コンポーネントでハンドラーがオプションになっている場合は、内部の props も厳密にしてください。次のようなオプション呼び出しを:
onRename?.(id, name)
必須の呼び出しに置き換えます。
onRename(id, name)
7. サポートされていない状態へのフォールバック処理を削除する
props を必須にしたら、型が広がっていたことだけを理由に存在していた防御的なフォールバックを削除してください。
例:
new Set(expandedIds ?? defaultExpandedIds ?? [])
は、次のようにします。
new Set(expandedIds)
items && items.length > 0
は、次のようにします。
items.length > 0
8. 同じ prop 型を共有するすべてのバリアントを更新する
複数のコンポーネントが広すぎる prop 型を共有している場合は、まとめて更新し、すべてが同じ実際のコードパスの契約を強制するようにしてください。
9. テストとストーリーを実際のコードに合わせる
props を絞り込んだ後にストーリーやテストが失敗する場合は、現実的なハンドラーと状態を渡して修正してください。テストの準備を減らすために、実際のコードパスで使う props を再びオプションにしてはいけません。
ストーリー/テストの準備が冗長に感じられる場合は、厳密な実際のコードパスの契約を満たすテストヘルパーまたはフィクスチャを作成してください。ヘルパーはサポートコードに置き、コンポーネント API を緩めないでください。
10. 変更を検証する
変更したパッケージ、および変更したコンポーネントを使用する各実アプリ/パッケージに対して、パッケージレベルの型チェックを実行してください。
利用可能な場合は、リポジトリ固有の検証コマンドを使用してください。このモノレポでは、次を優先してください。
bun --bun run typecheck --filter <package>
11. レスポンスを整える
CI エージェントとして実行する場合は、最終レスポンスを references/response-template.md に従って整えてください。このレスポンスは PR の本文になります。
以下を含めてください。
- 絞り込んだコンポーネント数の概要
- 変更内容と根拠をまとめた表
- 絞り込みの根拠となる実際の呼び出し箇所
- 更新が必要だったサポートコード(ストーリー/テスト)
- 検証結果
- リスク評価
レビューチェックリスト
- 変更した prop 型は、テスト以外の実際の呼び出し箇所と Storybook 以外の呼び出し箇所から導出されている。
- 常に描画される操作では、オプションのコールバックが削除されている。
- メニュー項目やボタンが、ハンドラーの欠如によって操作不能にならない。
- 削除した props は実際のコードパスで使用されていない。
- Null 許容は、現在フォーカスされている項目がない場合など、実際に存在する状態に限られている。
- 型は手動で重複定義するのではなく、可能な限り導出または抽出されている。
- 型推論によって意図した状態が拡大したり不明瞭になったりする場合は、
useState<T>(...)が使用されている。 - 共有するバリアントが同じ絞り込まれた契約に対してコンパイルされる。
- 共有パッケージと、それを使用する実アプリ/パッケージの型チェックが通る。
避けるべきアンチパターン
- ストーリーで省略できるように、コールバックをオプションにする。
onAction?.(...)を呼び出すメニュー項目を描画する。- 実際のコードでは状態が制御されているのに、Storybook 用の
default*props を追加する。 - 必須の実際の状態の欠如を隠すために、
?? []や?? 0を使用する。 - 実際のコードで使われる API 形式が 1 つしかないのに、複数の API 形式を受け入れる。
- 純粋コンポーネントを、契約が緩いモックコンポーネントとして扱う。