mcpField
R
react-native

react-native

github.com/alinaqi開発710565 回インストール3 週間前に更新
リポジトリ

react-native について

React Native開発向けのスキルで、プロジェクト構成、コンポーネントと状態管理のパターン、テスト、プラットフォーム固有コードの規則を解説します。React Nativeアプリの設計と実装の指針として利用できます。

主な機能

  1. 01プロジェクト構成の指針
  2. 02関数コンポーネントのパターン
  3. 03フックによるロジック分離
  4. 04状態管理の指針
  5. 05コンポーネントとフックのテスト
  6. 06プラットフォーム固有コードの規則

活用シーン

  1. 01React Nativeプロジェクトの構成
  2. 02明示的なPropsを持つ再利用可能なコンポーネントの作成
  3. 03コンポーネントとフックのテスト
  4. 04プラットフォーム固有実装の分離

インストール

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

$npx skills add https://github.com/alinaqi/maggy --skill react-native

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

詳細

React Native スキル


プロジェクト構成

project/
├── src/
│   ├── core/                   # Pure business logic (no React)
│   │   ├── types.ts
│   │   └── services/
│   ├── components/             # Reusable UI components
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   └── index.ts
│   │   └── index.ts            # Barrel export
│   ├── screens/                # Screen components
│   │   ├── Home/
│   │   │   ├── HomeScreen.tsx
│   │   │   ├── useHome.ts      # Screen-specific hook
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── navigation/             # Navigation configuration
│   ├── hooks/                  # Shared custom hooks
│   ├── store/                  # State management
│   └── utils/                  # Utilities
├── __tests__/
├── android/
├── ios/
└── CLAUDE.md

コンポーネントのパターン

関数コンポーネントのみを使用
// Good - simple, testable
interface ButtonProps {
  label: string;
  onPress: () => void;
  disabled?: boolean;
}

export function Button({ label, onPress, disabled = false }: ButtonProps): JSX.Element {
  return (
    <Pressable onPress={onPress} disabled={disabled}>
      <Text>{label}</Text>
    </Pressable>
  );
}
ロジックをフックに切り出す
// useHome.ts - all logic here
export function useHome() {
  const [items, setItems] = useState<Item[]>([]);
  const [loading, setLoading] = useState(false);

  const refresh = useCallback(async () => {
    setLoading(true);
    const data = await fetchItems();
    setItems(data);
    setLoading(false);
  }, []);

  return { items, loading, refresh };
}

// HomeScreen.tsx - pure presentation
export function HomeScreen(): JSX.Element {
  const { items, loading, refresh } = useHome();
  
  return (
    <ItemList items={items} loading={loading} onRefresh={refresh} />
  );
}
Props のインターフェースを必ず明示する
// Always define props interface, even if simple
interface ItemCardProps {
  item: Item;
  onPress: (id: string) => void;
}

export function ItemCard({ item, onPress }: ItemCardProps): JSX.Element {
  ...
}

状態管理

まずローカル状態を使う
// Start with useState, escalate only when needed
const [value, setValue] = useState('');
グローバル状態には Zustand を使う(必要な場合)
// store/useAppStore.ts
import { create } from 'zustand';

interface AppState {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useAppStore = create<AppState>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}));
サーバー状態には React Query を使う
// hooks/useItems.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function useItems() {
  return useQuery({
    queryKey: ['items'],
    queryFn: fetchItems,
  });
}

export function useCreateItem() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: createItem,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['items'] });
    },
  });
}

テスト

React Native Testing Library でコンポーネントをテストする
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';

describe('Button', () => {
  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button label="Click me" onPress={onPress} />);
    
    fireEvent.press(getByText('Click me'));
    
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('does not call onPress when disabled', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button label="Click me" onPress={onPress} disabled />);
    
    fireEvent.press(getByText('Click me'));
    
    expect(onPress).not.toHaveBeenCalled();
  });
});
フックのテスト
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('increments counter', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
});

プラットフォーム固有のコード

Platform.select は必要最小限に使う
import { Platform } from 'react-native';

const styles = StyleSheet.create({
  shadow: Platform.select({
    ios: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
    },
    android: {
      elevation: 2,
    },
  }),
});
複雑な差異にはファイルを分ける
Component/
├── Component.tsx          # Shared logic
├── Component.ios.tsx      # iOS-specific
├── Component.android.tsx  # Android-specific
└── index.ts

React Native のアンチパターン

  • ❌ インラインスタイル ― StyleSheet.create を使う
  • ❌ render 内のロジック ― フックに切り出す
  • ❌ コンポーネントの深いネスト ― 階層を平坦化する
  • ❌ props 内の匿名関数 ― useCallback を使う
  • ❌ リストのキーにインデックスを使う ― 安定した ID を使う
  • ❌ 状態を直接変更する ― 必ず setter を使う
  • ❌ ビジネスロジックと UI を混在させる ― core/ は純粋に保つ
  • ❌ TypeScript のエラーを無視する ― 修正する
  • ❌ 大きなコンポーネント ― 小さな単位に分割する

コメント