mcpField
R
react-native

react-native

github.com/s-hiraoku開発1972 回インストール4 週間前に更新
リポジトリ

react-native について

React Native と Expo の開発者向けに、パフォーマンス重視のパターンをまとめています。リスト描画、アニメーション、ナビゲーション、UI、状態管理、モノレポでの依存関係配置を優先度順に扱います。

主な機能

  1. 01FlashList とメモ化されたリスト項目
  2. 02安定したコールバックと画像サイズ指定
  3. 03GPU に適したアニメーションパターン
  4. 04ネイティブナビゲーションと UI コンポーネント
  5. 05セレクターによる状態購読
  6. 06モノレポの依存関係配置ガイド

活用シーン

  1. 01大規模リストのスクロール改善
  2. 02アニメーションのカクつき軽減
  3. 03ネイティブナビゲーションとメニューの選択
  4. 04状態変化による不要な再レンダリングの抑制

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/s-hiraoku/synapse-a2a --skill react-native

インストール後、Claude Code で/react-nativeを入力して読み込まれたか確認します。

詳細

React Native

パフォーマンスを最優先した React Native と Expo のパターンを、影響度順にまとめています。

優先度 1:リストのパフォーマンス(重大)

リストは RN アプリで最も大きなパフォーマンスのボトルネックです。まずはここを適切に設定しましょう。

FlashList を使用する

大規模なリストでは、FlatList を @shopify/flash-list に置き換えます。

import { FlashList } from '@shopify/flash-list';

<FlashList
  data={items}
  renderItem={({ item }) => <ItemRow item={item} />}
  estimatedItemSize={80}
  keyExtractor={(item) => item.id}
/>
リスト項目をメモ化する

すべてのリスト項目をメモ化する必要があります。

const ItemRow = memo(function ItemRow({ item }: { item: Item }) {
  return (
    <View style={styles.row}>
      <Text>{item.title}</Text>
    </View>
  );
});
コールバックを安定させる

コールバックを切り出し、リスト項目内でのインラインオブジェクトを避けます。

// BAD: New function + new style object every render
<Pressable onPress={() => onSelect(item.id)} style={{ padding: 16 }}>

// GOOD: Stable references
const handlePress = useCallback(() => onSelect(item.id), [item.id, onSelect]);
<Pressable onPress={handlePress} style={styles.pressable}>
リスト内の画像を最適化する

適切なサイズ設定とキャッシュを使って expo-image を使用します。

import { Image } from 'expo-image';

<Image
  source={{ uri: item.thumbnailUrl }}
  style={styles.thumbnail}
  contentFit="cover"
  placeholder={item.blurhash}
  transition={200}
  recyclingKey={item.id}
/>
異種リストの項目タイプ

getItemType を使用して、FlashList がセルを効率よく再利用できるようにします。

<FlashList
  data={mixedItems}
  renderItem={renderItem}
  getItemType={(item) => item.type} // 'header' | 'content' | 'ad'
  estimatedItemSize={100}
/>

優先度 2:アニメーション(高)

GPU 専用のプロパティ

アニメーションさせるのは transform と opacity のみです。それ以外はすべてレイアウトを再計算します。

import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated';

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ scale: withSpring(isPressed.value ? 0.95 : 1) }],
  opacity: withSpring(isVisible.value ? 1 : 0),
}));
派生値

計算を重複させないよう、計算を伴うアニメーションには useDerivedValue を使用します。

const progress = useSharedValue(0);
const rotation = useDerivedValue(() => `${progress.value * 360}deg`);

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ rotate: rotation.value }],
}));
ジェスチャー処理

60 fps でジェスチャーを追跡するには react-native-gesture-handler を使用します。

import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const pan = Gesture.Pan()
  .onUpdate((e) => {
    translateX.value = e.translationX;
    translateY.value = e.translationY;
  })
  .onEnd(() => {
    translateX.value = withSpring(0);
    translateY.value = withSpring(0);
  });

// Use Gesture.Tap() instead of Pressable for animated press feedback
const tap = Gesture.Tap()
  .onBegin(() => { scale.value = withSpring(0.95); })
  .onFinalize(() => { scale.value = withSpring(1); });

優先度 3:ナビゲーション(高)

ネイティブナビゲーター

JavaScript ベースの代替手段より、常にネイティブのスタックとタブを優先します。

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

// BAD: JS-based stack (slower transitions, no native gestures)
import { createStackNavigator } from '@react-navigation/stack';

// GOOD: Native stack (native transitions + gestures)
const Stack = createNativeStackNavigator();
画面オプション

ヘッダーとアニメーションをネイティブで設定します。

<Stack.Screen
  name="Detail"
  component={DetailScreen}
  options={{
    headerLargeTitle: true,    // iOS large title
    animation: 'slide_from_right',
  }}
/>

優先度 4:UI パターン(高)

セーフエリア

あらゆるデバイスの形状に対して、セーフエリアを適切に処理します。

import { SafeAreaView } from 'react-native-safe-area-context';

// For scrollable content
<SafeAreaView edges={['top']} style={{ flex: 1 }}>
  <ScrollView contentInsetAdjustmentBehavior="automatic">
    {children}
  </ScrollView>
</SafeAreaView>
ネイティブモーダル

JavaScript のオーバーレイではなく、ネイティブのモーダル表示を使用します。

<Stack.Screen
  name="Settings"
  component={SettingsScreen}
  options={{ presentation: 'modal' }}
/>
ネイティブメニュー

カスタムのドロップダウンコンポーネントではなく、コンテキストメニューを使用します。

import * as ContextMenu from 'zeego/context-menu';

<ContextMenu.Root>
  <ContextMenu.Trigger>
    <Pressable><Text>Options</Text></Pressable>
  </ContextMenu.Trigger>
  <ContextMenu.Content>
    <ContextMenu.Item key="edit" onSelect={handleEdit}>
      <ContextMenu.ItemTitle>Edit</ContextMenu.ItemTitle>
    </ContextMenu.Item>
    <ContextMenu.Item key="delete" onSelect={handleDelete} destructive>
      <ContextMenu.ItemTitle>Delete</ContextMenu.ItemTitle>
    </ContextMenu.Item>
  </ContextMenu.Content>
</ContextMenu.Root>
TouchableOpacity より Pressable を優先する
// BAD: Legacy touch component
<TouchableOpacity onPress={onPress}>{children}</TouchableOpacity>

// GOOD: Modern Pressable with feedback
<Pressable
  onPress={onPress}
  style={({ pressed }) => [styles.button, pressed && styles.pressed]}
  android_ripple={{ color: 'rgba(0,0,0,0.1)' }}
>
  {children}
</Pressable>

優先度 5:状態管理(中)

再レンダリングを最小限にする

必要な状態のみを購読します。

// BAD: Re-renders on any store change
const store = useStore();
return <Text>{store.user.name}</Text>;

// GOOD: Selector extracts only needed value
const name = useStore((s) => s.user.name);
return <Text>{name}</Text>;
React Compiler との互換性

Reanimated と React Compiler を使用する場合:

// Destructure shared value functions for compiler compatibility
const { value } = useSharedValue(0);

// Use worklet directive for Reanimated callbacks
const animatedStyle = useAnimatedStyle(() => {
  'worklet';
  return { opacity: value };
});

優先度 6:モノレポ(中)

ネイティブ依存関係

ネイティブ依存関係は共有パッケージではなく、アプリパッケージに配置します。

packages/
  ui/              # Pure React components (no native deps)
  shared/          # Business logic, types
apps/
  mobile/          # Native deps (expo-image, reanimated) here
依存関係のバージョンを統一する

モノレポ全体で、依存関係ごとに 1 つのバージョンのみを使用するようにします。

// Root package.json
{
  "resolutions": {
    "react-native": "0.76.x",
    "react-native-reanimated": "3.x"
  }
}

クイックリファレンス

問題解決策優先度
スクロールが遅いリストFlashList + メモ化した項目重大
リスト内のインラインオブジェクトStyleSheet に切り出す重大
カクつくアニメーションtransform/opacity のみを使用高
JavaScript ベースのナビゲーションネイティブのスタック/タブ高
カスタムのドロップダウンメニューネイティブのコンテキストメニュー高
ストア全体の購読セレクター中
共有パッケージ内のネイティブ依存関係アプリパッケージに移動する中

コメント