mobile-app

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mobile App Development Expertise

移动应用开发专业指南

Overview

概述

A guide for developing mobile apps based on web development experience. Develop for iOS and Android simultaneously using cross-platform frameworks.

一份基于Web开发经验的移动应用开发指南。 使用跨平台框架同时为iOS和Android进行开发。

Framework Selection Guide

框架选择指南

Framework Selection by Tier (v1.3.0)

按层级划分的框架选择(v1.3.0版)

FrameworkTierRecommendationUse Case
React Native (Expo)Tier 1⭐ PrimaryTypeScript ecosystem, AI tools
React Native CLITier 1RecommendedNative module needs
FlutterTier 2SupportedMulti-platform (6 OS), performance
AI-Native Recommendation: React Native with TypeScript
  • Full Copilot/Claude support
  • Extensive npm ecosystem
  • 20:1 developer availability vs Dart
Performance Recommendation: Flutter
  • Impeller rendering engine
  • Single codebase for 6 platforms
  • Smaller bundles
FrameworkTier推荐等级适用场景
React Native (Expo)Tier 1⭐ 首选TypeScript生态系统、AI工具
React Native CLITier 1推荐需要原生模块
FlutterTier 2支持多平台(6种操作系统)、性能优先
原生AI支持推荐:React Native + TypeScript
  • 完整支持Copilot/Claude
  • 丰富的npm生态系统
  • 开发者可用度是Dart的20:1
性能推荐:Flutter
  • Impeller渲染引擎
  • 单一代码库适配6种平台
  • 更小的安装包

Level-wise Recommendations

按开发阶段的推荐

Starter → Expo (React Native) [Tier 1]
  - Simple setup, can leverage web knowledge
  - Full AI tool support

Dynamic → Expo + EAS Build [Tier 1] or Flutter [Tier 2]
  - Includes server integration, production build support
  - Choose Flutter for multi-platform needs

Enterprise → React Native CLI [Tier 1] or Flutter [Tier 2]
  - Complex native features, performance optimization needed
  - Flutter for consistent cross-platform UI

入门阶段 → Expo (React Native) [Tier 1]
  - 配置简单,可复用Web开发知识
  - 完整支持AI工具

进阶阶段 → Expo + EAS Build [Tier 1] 或 Flutter [Tier 2]
  - 包含服务端集成、生产构建支持
  - 如需多平台适配选择Flutter

企业级阶段 → React Native CLI [Tier 1] 或 Flutter [Tier 2]
  - 需要复杂原生功能、性能优化
  - 追求跨平台UI一致性选Flutter

Expo (React Native) Guide

Expo (React Native) 指南

Project Creation

项目创建

bash
undefined
bash
undefined

Install Expo CLI

Install Expo CLI

npm install -g expo-cli
npm install -g expo-cli

Create new project

Create new project

npx create-expo-app my-app cd my-app
npx create-expo-app my-app cd my-app

Start development server

Start development server

npx expo start
undefined
npx expo start
undefined

Folder Structure

文件夹结构

my-app/
├── app/                    # Expo Router pages
│   ├── (tabs)/            # Tab navigation
│   │   ├── index.tsx      # Home tab
│   │   ├── explore.tsx    # Explore tab
│   │   └── _layout.tsx    # Tab layout
│   ├── _layout.tsx        # Root layout
│   └── +not-found.tsx     # 404 page
├── components/            # Reusable components
├── hooks/                 # Custom hooks
├── constants/             # Constants
├── assets/               # Images, fonts, etc.
├── app.json              # Expo configuration
└── package.json
my-app/
├── app/                    # Expo Router 页面
│   ├── (tabs)/            # 标签页导航
│   │   ├── index.tsx      # 首页标签
│   │   ├── explore.tsx    # 探索页标签
│   │   └── _layout.tsx    # 标签页布局
│   ├── _layout.tsx        # 根布局
│   └── +not-found.tsx     # 404页面
├── components/            # 可复用组件
├── hooks/                 # 自定义hooks
├── constants/             # 常量
├── assets/               # 图片、字体等资源
├── app.json              # Expo配置文件
└── package.json

Navigation Patterns

导航模式

typescript
// app/_layout.tsx - Stack navigation
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
    </Stack>
  );
}
typescript
// app/(tabs)/_layout.tsx - Tab navigation
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <Ionicons name="home" color={color} size={24} />,
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color }) => <Ionicons name="person" color={color} size={24} />,
        }}
      />
    </Tabs>
  );
}
typescript
// app/_layout.tsx - 栈式导航
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
    </Stack>
  );
}
typescript
// app/(tabs)/_layout.tsx - 标签页导航
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <Ionicons name="home" color={color} size={24} />,
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color }) => <Ionicons name="person" color={color} size={24} />,
        }}
      />
    </Tabs>
  );
}

Styling Patterns

样式模式

typescript
// Basic StyleSheet
import { StyleSheet, View, Text } from 'react-native';

export function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
});
typescript
// NativeWind (Tailwind for RN) - Recommended
import { View, Text } from 'react-native';

export function MyComponent() {
  return (
    <View className="flex-1 p-4 bg-white">
      <Text className="text-2xl font-bold">Hello</Text>
    </View>
  );
}
typescript
// Basic StyleSheet
import { StyleSheet, View, Text } from 'react-native';

export function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
});
typescript
// NativeWind (Tailwind for RN) - Recommended
import { View, Text } from 'react-native';

export function MyComponent() {
  return (
    <View className="flex-1 p-4 bg-white">
      <Text className="text-2xl font-bold">Hello</Text>
    </View>
  );
}

API Integration

API集成

typescript
// hooks/useApi.ts
import { useState, useEffect } from 'react';

export function useApi<T>(endpoint: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}${endpoint}`);
        if (!response.ok) throw new Error('API Error');
        const json = await response.json();
        setData(json);
      } catch (e) {
        setError(e as Error);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [endpoint]);

  return { data, loading, error };
}
typescript
// hooks/useApi.ts
import { useState, useEffect } from 'react';

export function useApi<T>(endpoint: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}${endpoint}`);
        if (!response.ok) throw new Error('API Error');
        const json = await response.json();
        setData(json);
      } catch (e) {
        setError(e as Error);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [endpoint]);

  return { data, loading, error };
}

Authentication Pattern

认证模式

typescript
// context/AuthContext.tsx
import { createContext, useContext, useState, useEffect } from 'react';
import * as SecureStore from 'expo-secure-store';

interface AuthContextType {
  user: User | null;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    // Check for stored token on app start
    const loadToken = async () => {
      const token = await SecureStore.getItemAsync('authToken');
      if (token) {
        // Load user info with token
      }
    };
    loadToken();
  }, []);

  const signIn = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const { token, user } = await response.json();
    await SecureStore.setItemAsync('authToken', token);
    setUser(user);
  };

  const signOut = async () => {
    await SecureStore.deleteItemAsync('authToken');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext)!;

typescript
// context/AuthContext.tsx
import { createContext, useContext, useState, useEffect } from 'react';
import * as SecureStore from 'expo-secure-store';

interface AuthContextType {
  user: User | null;
  signIn: (email: string, password: string) => Promise<void>;
  signOut: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    // Check for stored token on app start
    const loadToken = async () => {
      const token = await SecureStore.getItemAsync('authToken');
      if (token) {
        // Load user info with token
      }
    };
    loadToken();
  }, []);

  const signIn = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const { token, user } = await response.json();
    await SecureStore.setItemAsync('authToken', token);
    setUser(user);
  };

  const signOut = async () => {
    await SecureStore.deleteItemAsync('authToken');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext)!;

Flutter Guide

Flutter 指南

Project Creation

项目创建

bash
undefined
bash
undefined

After installing Flutter SDK

After installing Flutter SDK

flutter create my_app cd my_app
flutter create my_app cd my_app

Start development server

Start development server

flutter run
undefined
flutter run
undefined

Folder Structure

文件夹结构

my_app/
├── lib/
│   ├── main.dart           # App entry point
│   ├── app/
│   │   ├── app.dart        # MaterialApp setup
│   │   └── routes.dart     # Route definitions
│   ├── features/           # Feature-based folders
│   │   ├── auth/
│   │   │   ├── screens/
│   │   │   ├── widgets/
│   │   │   └── providers/
│   │   └── home/
│   ├── shared/
│   │   ├── widgets/        # Common widgets
│   │   ├── services/       # API services
│   │   └── models/         # Data models
│   └── core/
│       ├── theme/          # Theme settings
│       └── constants/      # Constants
├── assets/                 # Images, fonts
├── pubspec.yaml           # Dependency management
└── android/ & ios/        # Native code
my_app/
├── lib/
│   ├── main.dart           # 应用入口
│   ├── app/
│   │   ├── app.dart        # MaterialApp配置
│   │   └── routes.dart     # 路由定义
│   ├── features/           # 基于功能的文件夹
│   │   ├── auth/
│   │   │   ├── screens/
│   │   │   ├── widgets/
│   │   │   └── providers/
│   │   └── home/
│   ├── shared/
│   │   ├── widgets/        # 通用组件
│   │   ├── services/       # API服务
│   │   └── models/         # 数据模型
│   └── core/
│       ├── theme/          # 主题设置
│       └── constants/      # 常量
├── assets/                 # 图片、字体
├── pubspec.yaml           # 依赖管理
└── android/ & ios/        # 原生代码

Basic Widget Patterns

基础组件模式

dart
// lib/features/home/screens/home_screen.dart
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: const Center(
        child: Text('Hello, Flutter!'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}
dart
// lib/features/home/screens/home_screen.dart
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: const Center(
        child: Text('Hello, Flutter!'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

State Management (Riverpod)

状态管理(Riverpod)

dart
// lib/providers/counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}
dart
// Usage
class CounterScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Text('Count: $count');
  }
}

dart
// lib/providers/counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}
dart
// Usage
class CounterScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Text('Count: $count');
  }
}

Web vs Mobile Differences

Web与移动应用的差异

UI/UX Differences

UI/UX差异

ElementWebMobile
ClickonClickonPress
Scrolloverflow: scrollScrollView / FlatList
InputinputTextInput
Linksa hrefLink / navigation
Layoutdiv + CSSView + StyleSheet
元素Web移动应用
点击事件onClickonPress
滚动overflow: scrollScrollView / FlatList
输入框inputTextInput
链接a hrefLink / 导航API
布局div + CSSView + StyleSheet

Navigation Differences

导航差异

Web: URL-based (browser back button)
Mobile: Stack-based (screen stacking)

Web: /users/123
Mobile: navigation.navigate('User', { id: 123 })
Web: 基于URL(浏览器返回键)
移动应用: 基于栈(页面堆叠)

Web: /users/123
移动应用: navigation.navigate('User', { id: 123 })

Storage Differences

存储差异

Web: localStorage, sessionStorage, Cookie
Mobile: AsyncStorage, SecureStore, SQLite

⚠️ SecureStore is required for sensitive info on mobile!

Web: localStorage, sessionStorage, Cookie
移动应用: AsyncStorage, SecureStore, SQLite

⚠️ 移动应用中敏感信息必须使用SecureStore!

Build & Deployment

构建与部署

Expo EAS Build

Expo EAS Build

bash
undefined
bash
undefined

Install EAS CLI

Install EAS CLI

npm install -g eas-cli
npm install -g eas-cli

Login

Login

eas login
eas login

Configure build

Configure build

eas build:configure
eas build:configure

iOS build

iOS build

eas build --platform ios
eas build --platform ios

Android build

Android build

eas build --platform android
eas build --platform android

Submit to stores

Submit to stores

eas submit --platform ios eas submit --platform android
undefined
eas submit --platform ios eas submit --platform android
undefined

Environment Variables

环境变量

json
// app.json
{
  "expo": {
    "extra": {
      "apiUrl": "https://api.example.com"
    }
  }
}
typescript
// Usage
import Constants from 'expo-constants';

const apiUrl = Constants.expoConfig?.extra?.apiUrl;

json
// app.json
{
  "expo": {
    "extra": {
      "apiUrl": "https://api.example.com"
    }
  }
}
typescript
// Usage
import Constants from 'expo-constants';

const apiUrl = Constants.expoConfig?.extra?.apiUrl;

Mobile PDCA Checklist

移动应用PDCA检查清单

Phase 1: Schema

阶段1:架构设计

□ Identify data that needs offline caching
□ Define sync conflict resolution strategy
□ 确定需要离线缓存的数据
□ 定义同步冲突解决策略

Phase 3: Mockup

阶段3:原型设计

□ Follow iOS/Android native UX guidelines
□ Consider gestures (swipe, pinch, etc.)
□ Layout for different screen sizes (phone, tablet)
□ 遵循iOS/Android原生UX指南
□ 考虑手势操作(滑动、捏合等)
□ 适配不同屏幕尺寸(手机、平板)

Phase 6: UI

阶段6:UI开发

□ Keyboard handling (screen adjustment during input)
□ Safe Area handling (notch, home button area)
□ Handle platform-specific UI differences
□ 键盘处理(输入时调整屏幕)
□ 安全区域处理(刘海屏、Home键区域)
□ 处理平台特定UI差异

Phase 7: Security

阶段7:安全

□ Store sensitive info with SecureStore
□ Certificate Pinning (if needed)
□ App obfuscation settings
□ 使用SecureStore存储敏感信息
□ 证书绑定(如需)
□ 应用混淆设置

Phase 9: Deployment

阶段9:部署

□ Follow App Store review guidelines
□ Prepare Privacy Policy URL
□ Prepare screenshots, app description

□ 遵循应用商店审核指南
□ 准备隐私政策URL
□ 准备截图、应用描述

Frequently Asked Questions

常见问题

Q: Can I convert a web project to an app?

Q: 能否将Web项目转为移动应用?

A: Recommend separate project over full conversion
   - APIs can be shared
   - UI needs to be rewritten (for native UX)
   - Business logic can be shared
A: 建议新建项目而非直接转换
   - API可共享
   - UI需要重写(以适配原生UX)
   - 业务逻辑可共享

Q: Should I use Expo or React Native CLI?

Q: 应该使用Expo还是React Native CLI?

A: Start with Expo!
   - 90%+ of apps are sufficient with Expo
   - Can eject later if needed
   - Use CLI only when native modules are absolutely required
A: 从Expo开始!
   - 90%以上的应用使用Expo即可满足需求
   - 后续可按需导出为CLI项目
   - 仅当必须使用原生模块时才选择CLI

Q: How long does app review take?

Q: 应用审核需要多长时间?

A:
   - iOS: 1-7 days (average 2-3 days)
   - Android: Few hours ~ 3 days

   ⚠️ First submission has high rejection possibility → Follow guidelines carefully!

A:
   - iOS: 1-7天(平均2-3天)
   - Android: 数小时 ~ 3天

   ⚠️ 首次提交被拒绝的概率较高 → 请严格遵循指南!

Requesting from Claude

向Claude提出需求的示例

Project Creation

项目创建

"Set up a [app description] app project with React Native + Expo.
Configure with 3 tab navigation (Home, Search, Profile)."
"使用React Native + Expo搭建一个[应用描述]的项目。
配置3个标签页导航(首页、搜索、个人中心)。"

Screen Implementation

页面实现

"Implement [screen name] screen.
- Display [content] at the top
- Display [list/form/etc.] in the middle
- [Button/navigation] at the bottom"
"实现[页面名称]页面。
- 顶部展示[内容]
- 中部展示[列表/表单等]
- 底部添加[按钮/导航]"

API Integration

API集成

"Implement screen integrating with [API endpoint].
- Show loading state
- Handle errors
- Support pull-to-refresh"
"实现集成[API端点]的页面。
- 展示加载状态
- 处理错误
- 支持下拉刷新"