react-native-patterns について
保守しやすいモバイルコンポーネントを構築する React Native 開発者向けのスキルです。操作、リスト、ナビゲーション、アクセシビリティ、チャット形式の UI を、React Native 固有の観点から扱います。
主な機能
- 01Pressable のタッチ操作パターン
- 02FlashList と ScrollView の指針
- 03ボトムシートモーダルのパターン
- 04アクセシビリティ要件
- 05チャット形式のコンポーネントパターン
- 06Expo Router のナビゲーションパターン
活用シーン
- 01操作可能なモバイル画面の構築
- 02動的リストの効率的な表示
- 03アクセシブルな操作部品とナビゲーションの追加
- 04リッチカード付きチャット画面の設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dralgorhythm/ claude-agentic-framework --skill react-native-patternsインストール後、Claude Code で/react-native-patternsを入力して読み込まれたか確認します。
詳細
React Native のパターン
概要
保守しやすい React Native コンポーネントを構築するためのパターンです。タッチ操作(Pressable)、リストのレンダリング(FlashList)、モーダルのパターン(ボトムシート)、アクセシビリティ、ナビゲーション、および Web 版の React とは異なる、React Native 固有の要件を扱います。
React Web のパターン(フック、コンテキスト、コンポジション)については、react-patterns スキルを参照してください。
ワークフロー
インタラクティブな画面の構築:
- SafeAreaView を設定するか、セーフエリアのインセットを適用します
- すべてのインタラクティブな要素に Pressable を使用します(最小 44pt)
- 主要なアクションに触覚フィードバックを追加します
- リストには FlashList、固定コンテンツには ScrollView を使用します
- 詳細画面やモーダル画面にはボトムシートを実装します
- すべてのインタラクティブな要素にアクセシビリティラベルを追加します
- デバイスでタッチターゲットとスクロールのパフォーマンスをテストします
ガイダンス
TouchableOpacity ではなく Pressable
Pressable は標準のタッチコンポーネントです。TouchableOpacity は非推奨です。
主な原則:
- すべてのインタラクティブな要素で、タッチターゲットは最小 44ptにします
hitSlopを使って、見た目の範囲よりタッチ領域を広げます:hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}style関数でフィードバックを設定します:style={({ pressed }) => [pressed && { opacity: 0.7 }]}- または NativeWind を使用します:
className="active:opacity-70" - 主要なアクションでは、押下時に触覚フィードバックを追加します:
onPress={() => { Haptics.impactAsync(); doAction(); }}
テキストのみの文字列ルール
React Native では、表示されるすべてのテキストを <Text> で囲む必要があります:
✅ <View><Text>Hello</Text></View>
❌ <View>Hello</View> // Crashes at runtime
これは条件付きレンダリング、文字列補間、JSX 式にも適用されます。文字列は必ず Text で囲んでください。
ScrollView のパターン
className の適用先は 2 つあります:
className— 外側のコンテナ(flex、背景)contentContainerClassName— 内側のコンテンツ(パディング、間隔、配置)
固定された動的でないコンテンツの画面には ScrollView を使用します。動的なリストには FlashList を使用します。
カスタムのスクロールインジケーターを使う場合は、表示をすっきりさせるために showsVerticalScrollIndicator={false} を設定します。
FlashList のパターン
高パフォーマンスなリストのレンダリング:
estimatedItemSizeは必須です。項目の平均高さをポイント単位で見積もりますrenderItemは{ item, index }を受け取ります。レンダー関数は純粋に保ちますkeyExtractor— データから一意の文字列 ID を使用しますcontentContainerClassName— 内側のコンテンツに NativeWind のスタイルを適用しますItemSeparatorComponent— 項目間の間隔を統一しますListEmptyComponent— 空の状態を適切に表示します
モーダルとしてのボトムシート
@gorhom/bottom-sheet は Web のモーダルダイアログに代わるものです:
snapPoints配列を定義します:['25%', '50%', '90%']- 命令型の制御には、
BottomSheetModalProviderとともにBottomSheetModalを使用します - スクロール可能なシートのコンテンツには
BottomSheetScrollViewを使用します - タップして閉じるオーバーレイには
BottomSheetBackdropを使用します - 表示:
bottomSheetRef.current?.present() - 閉じる:
bottomSheetRef.current?.dismiss()
expo-image のパターン
すべての画像表示に使用します:
- リモート画像:
source={{ uri: 'https://...' }} - ローカル画像:
source={require('../assets/image.png')} - Blurhash プレースホルダー:読み込み中の状態には
placeholder="LKO2?U%2Tw=w]~RBVZRi}"を使用します - カードのサムネイルには
contentFit="cover"、画像全体には"contain"を使用します - 読み込み時のスムーズなフェードインには
transition={300}を使用します
アクセシビリティ
インタラクティブな要素に必要なアクセシビリティプロパティ:
| プロパティ | 用途 | 例 |
|---|---|---|
accessibilityRole | セマンティックな役割 | "button"、"link"、"image" |
accessibilityLabel | スクリーンリーダー用テキスト | "Open settings" |
accessibilityHint | アクションの説明 | "Opens the settings screen" |
accessibilityState | 動的な状態 | { selected: true, disabled: false } |
accessible | アクセシビリティ対応として指定 | Pressable ではデフォルトで true |
ガイドライン:
- すべての Pressable 要素に
accessibilityLabelが必要です - 画像には、内容を説明する
accessibilityLabelが必要です - 画面タイトルには
accessibilityRole="header"を使用します - トグル/チェックボックス:
accessibilityState={{ checked: isChecked }}を設定します - 無効な要素:
accessibilityState={{ disabled: true }}を設定します
チャット中心のコンポーネントパターン
デモでは、会話スレッドを主要なUIとするチャット中心のインターフェースを使用します。
チャット画面のレイアウト
3つの領域で構成されます。
- ヘッダー:最小限の要素で構成します。タイトル、戻るナビゲーション、任意のステータスを表示します。上部のセーフエリアインセットを使用します。
- メッセージストリーム:会話にはFlashListまたは反転したScrollViewを使用します。エージェントとユーザーのメッセージが交互に表示されます。リッチコンテンツはエージェントのメッセージコンポーネント内に埋め込みます。
- 入力バー:画面下部に固定したテキスト入力欄です。iOSでは
KeyboardAvoidingView、Androidではandroid:windowSoftInputMode="adjustResize"を使用します。ホームインジケーターに合わせて下部のセーフエリアインセットを使用します。任意で、テキスト入力欄の上にクイックアクションチップを配置できます。
メッセージバブル
エージェントとユーザーのバブルは、配置、色、形状で見分けられるようにします。
- エージェントのバブル:左寄せで、ニュートラルな背景色を使用します。子コンポーネント(カード、要約、アクションボタン)を含めることができます。
- ユーザーのバブル:右寄せで、アクセントカラーの背景を使用します。テキストのみを表示します。
- バブルは追加時に
FadeInDown.duration(300)でアニメーション表示します。
インラインのリッチカード
エージェントのメッセージバブル内に子要素として表示する、コンパクトなデータカードです。
- 写真のサムネイル(expo-image)+タイトル+主要な指標+ステータスバッジ
- タップするとボトムシートで詳細を表示します。
- カードが複数ある場合は、メッセージ内に横向きの
ScrollViewを配置するか、縦に積み重ねます。 - 順番に表示するアニメーション:
FadeInDown.delay(index * 50).duration(300)
要約カード
会話の進行に合わせて更新される、合計やステータスを表示します。
- 集計状態を表示します:保持($X)、売却($Y)、寄付($Z)、進捗(N%)
- 常に表示できるよう、入力バーの上に固定要素として描画できます。
- 更新時には内容をクロスフェードします(150msのタイミング)。
プロンプトカード
アクションボタンを埋め込んだ、エージェントからの提案です。
- テキストのプロンプトと、インラインのアクションボタンを1~3個表示します。
- ボタンは44ptのタップ領域を持つPressableとし、押下時に触覚フィードバックを提供します。
- タップ後、プロンプトカードを更新して、確定したアクションを表示します。
- 例:「集荷の選択肢を探しましょうか?」 → [はい] [今はしない]
比較ビュー
メッセージ内に、変更前後または左右並列のコンテンツを表示します。
- ラベル(変更前/変更後)付きのexpo-imageを2つ使用します。
- 差分の注釈を任意で追加します(例:「新しい傷を検出しました」)。
タイムラインビュー
エージェントのメッセージ内にインラインで表示する、時系列のイベント一覧です。
- コンパクト表示:イベントごとに日付+タイトル+アイコンを表示します。
- 展開可能:タップすると詳細を表示します(インライン展開またはボトムシート)。
- イベント間を縦線でつなぎます。
通知メッセージ
スレッド内に新しいメッセージとして表示される、エージェントからのプロアクティブな通知です。
- 通常のエージェントメッセージと区別できるよう、特徴的なスタイル(アイコン+強調色)を使用します。
- インラインのアクションボタンを含めることができます。
- 例:「嵐の季節は6週間後に始まります。地下室にはまだ湿気のリスクがあります。」
コンポーネントからのナビゲーション
Expo RouterのuseRouter()フックを使用します。
router.push('/path')— 次の画面に移動します(スタックに追加)。router.replace('/path')— 現在の画面を置き換えます。router.back()— 前の画面に戻ります。router.canGoBack()— 戻るナビゲーションが可能か確認します。- パラメーターを渡します:
router.push({ pathname: '/detail/[id]', params: { id: '123' } })
ベストプラクティス
TouchableOpacityやTouchableHighlightではなく、常にPressableを使用します。- 最小44ptのタップ領域を確保します。見た目の要素が小さい場合は
hitSlopを使用します。 - 表示するすべての文字列を
<Text>で囲みます。JSX内に文字列をそのまま記述しないでください。 - すべての画像に
expo-imageを使用します(RNのImageは使用しないでください)。 - すべてのインタラクティブ要素に
accessibilityLabelを追加します。 - 10項目を超えるリストには、
estimatedItemSizeを指定してFlashListを使用します。 - ScrollView/FlashListの内側のスタイルには
contentContainerClassNameを使用します。 - 触覚フィードバックは控えめにし、主要なアクションにのみ追加します。
- 実機でタップ領域をテストします(シミュレーターではタップ位置の精度が不十分です)。
アンチパターン
TouchableOpacityを使用する(非推奨です。Pressableを使用してください)hitSlopで補正せずに、44pt 未満のタッチターゲットを使用する<Text>でラップせずに文字列をそのまま使う(実行時にクラッシュします)- RN の
Imageをexpo-imageの代わりに使用する(キャッシュも blurhash もありません) - FlashList で
estimatedItemSizeの指定を忘れる - 大規模なデータセットに
FlatListを使用する(FlashList のほうが高速です) - インタラクティブな要素に
accessibilityLabelを指定しない - 複雑な選択肢に
Alert.alert()を使用する(代わりにボトムシートを使用してください) - ScrollView の
classNameにパディングを指定する(contentContainerClassNameを使用してください) - FlashList の
renderItemでインラインの無名関数を使用する(再レンダリングが発生します。コンポーネントを切り出してください)