mcpField
M
mobile-development

mobile-development

github.com/rohitg00開発2.8k205 回インストール24 週間前に更新
リポジトリ

mobile-development について

mobile-developmentは、React NativeやFlutterでモバイル画面を開発する人向けの資料です。コンポーネント、ナビゲーション、レスポンシブレイアウト、プラットフォーム差異、よくある問題を扱います。

主な機能

  1. 01React Nativeのコンポーネントとリストのパターン
  2. 02型付きReact Nativeナビゲーション
  3. 03Flutterのウィジェット構成
  4. 04画面サイズに応じたレイアウト
  5. 05プラットフォーム固有のスタイルと動作
  6. 06モバイル開発チェックリスト

活用シーン

  1. 01React Native画面の構成
  2. 02型付きアプリナビゲーションの設定
  3. 03スマートフォンやタブレット向けのレイアウト調整
  4. 04モバイルコードのよくある問題の確認

インストール

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

$npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill mobile-development

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

詳細

モバイル開発

React Native コンポーネントの構造

import { View, Text, FlatList, StyleSheet, Platform } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

interface Product {
  id: string;
  name: string;
  price: number;
  image: string;
}

function ProductList({ products }: { products: Product[] }) {
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={products}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => <ProductCard product={item} />}
        contentContainerStyle={styles.list}
        ItemSeparatorComponent={() => <View style={styles.separator} />}
        ListEmptyComponent={<EmptyState message="No products found" />}
        initialNumToRender={10}
        maxToRenderPerBatch={10}
        windowSize={5}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  list: {
    padding: 16,
  },
  separator: {
    height: 12,
  },
});

スクロール可能なリストには FlatList を使用してください(.map() を使った ScrollView は使用しないでください)。大規模なリストでは windowSize と maxToRenderPerBatch を設定してください。

React Native のナビゲーション

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

type RootStackParams = {
  Tabs: undefined;
  ProductDetail: { productId: string };
  Cart: undefined;
};

const Stack = createNativeStackNavigator<RootStackParams>();
const Tab = createBottomTabNavigator();

function TabNavigator() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Search" component={SearchScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Tabs" component={TabNavigator} options={{ headerShown: false }} />
        <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
        <Stack.Screen name="Cart" component={CartScreen} options={{ presentation: "modal" }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Flutter ウィジェットのパターン

class ProductCard extends StatelessWidget {
  final Product product;
  final VoidCallback onTap;

  const ProductCard({
    super.key,
    required this.product,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Card(
        elevation: 2,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ClipRRect(
              borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
              child: Image.network(
                product.imageUrl,
                height: 200,
                width: double.infinity,
                fit: BoxFit.cover,
                errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, size: 64),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(product.name, style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 4),
                  Text("\$${product.price.toStringAsFixed(2)}",
                      style: Theme.of(context).textTheme.bodyLarge),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

レスポンシブレイアウト

import { useWindowDimensions } from "react-native";

function useResponsive() {
  const { width } = useWindowDimensions();
  return {
    isPhone: width < 768,
    isTablet: width >= 768 && width < 1024,
    isDesktop: width >= 1024,
    columns: width < 768 ? 1 : width < 1024 ? 2 : 3,
  };
}

function ProductGrid({ products }: { products: Product[] }) {
  const { columns } = useResponsive();

  return (
    <FlatList
      data={products}
      numColumns={columns}
      key={columns}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ flex: 1, maxWidth: `${100 / columns}%`, padding: 8 }}>
          <ProductCard product={item} />
        </View>
      )}
    />
  );
}

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

import { Platform } from "react-native";

const styles = StyleSheet.create({
  shadow: Platform.select({
    ios: {
      shadowColor: "#000",
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
      shadowRadius: 4,
    },
    android: {
      elevation: 4,
    },
    default: {},
  }),
});

アンチパターン

  • 動的なリストに .map() を使った ScrollView を使用する(FlatList または SectionList を使用してください)
  • コンポーネントの近くに配置せず、すべての状態をグローバルストアに保存する
  • セーフエリア(ノッチ、ステータスバー、ホームインジケーター)に対応しない
  • レンダリングのたびにインラインスタイルを使う(StyleSheet.create で定義してください)
  • 重い計算で JS スレッドをブロックする(InteractionManager を使用してください)
  • プラットフォーム固有の UX の慣習を無視する(iOS の戻るスワイプ、Android の戻るボタン)

チェックリスト

  • スクロール可能なすべてのリストで keyExtractor を指定した FlatList を使用している
  • ナビゲーションのルートパラメーターを TypeScript で型付けしている
  • SafeAreaView でセーフエリアのインセットに対応している
  • スタイルをインラインオブジェクトではなく StyleSheet.create で定義している
  • レスポンシブレイアウトがスマートフォン、タブレット、デスクトップに適応している
  • Platform.select でプラットフォーム固有のスタイルに対応している
  • 画像をキャッシュし、エラー状態と読み込み状態を表示している
  • 重い計算を JS スレッドからオフロードしている

コメント