移动端与跨平台开发完全指南

NOTE

本文档涵盖移动端 Web 开发、跨平台框架(React Native / Flutter / Capacitor / Tauri)、PWA 及响应式设计策略,是 vibecoding 时代覆盖多端设备的完整指南。


技术概述与定位

移动端开发的现状与挑战

2026年的移动端开发格局已经发生了根本性变化。智能手机普及率接近饱和,用户对应用性能和体验的要求达到了前所未有的高度。根据最新统计数据:

  • 全球移动设备活跃用户超过 70亿
  • 移动端流量占比达到 60% 以上
  • 用户平均每天使用移动应用超过 4小时
  • 应用商店中应用数量超过 500万

移动端开发面临的核心挑战包括:

碎片化问题:Android 设备屏幕尺寸从 4 英寸到 12 英寸不等,iOS 设备从 iPhone SE 到 iPad Pro 屏幕尺寸差异巨大。

性能要求:用户对应用启动速度、交互响应流畅度的期望越来越高,研究表明,1秒的延迟可能导致高达 7% 的转化率下降。

跨平台需求:企业需要同时覆盖 iOS、Android、Web 多个平台,传统原生开发成本高昂。

移动端技术栈全景图

移动端开发技术栈
├── Web 原生技术
│   ├── 响应式 Web 设计 (RWD)
│   ├── 渐进式 Web 应用 (PWA)
│   ├── 移动端 Web 优化
│   └── Web APIs (Native Features)
│
├── 跨平台框架
│   ├── React Native (JavaScript/TypeScript)
│   │   ├── Expo (推荐开发工具)
│   │   └── React Native CLI
│   │
│   ├── Flutter (Dart)
│   │   └── Flutter SDK
│   │
│   ├── Capacitor (Web → Native)
│   │   └── Ionic
│   │
│   └── Tauri (Rust + Web)
│       └── Tauri CLI
│
├── 原生开发
│   ├── iOS (Swift/SwiftUI)
│   │   └── Xcode
│   │
│   └── Android (Kotlin/Jetpack Compose)
│       └── Android Studio
│
└── 开发与部署工具
    ├── Expo (React Native 生态)
    ├── Codemagic (CI/CD)
    ├── Firebase (后端即服务)
    └── App Store Connect / Google Play Console

技术选型决策框架

选择移动端技术需要综合考虑以下因素:

考虑维度说明权重
团队技能现有技术栈和团队经验
应用类型游戏、电商、工具、企业应用等
性能要求是否需要极致性能
发布渠道应用商店、独立分发
开发周期时间预算
长期维护预期应用生命周期
生态成熟度第三方库和社区支持

本文档的目标读者

本文档旨在帮助开发者:

  1. 理解移动端 Web 开发的核心技术:掌握响应式设计、PWA、移动端优化等技能
  2. 选择合适的跨平台方案:根据项目需求选择 React Native、Flutter、Capacitor 等框架
  3. 掌握移动端调试技能:使用现代开发工具高效调试移动应用
  4. 实施性能优化策略:确保应用在各种设备上流畅运行

移动端开发的技术趋势

Web 技术的崛起:随着浏览器引擎性能的提升和 Web API 的扩展,越来越多的应用选择 Web 技术栈。PWA 的成熟让 Web 应用能够提供接近原生的体验。

跨平台框架的成熟:React Native 和 Flutter 已经成为成熟的跨平台解决方案。Expo 和 Flutter DevTools 等工具的完善大大降低了开发门槛。

AI 辅助开发:AI 代码助手正在改变移动端开发的方式,自动化测试、代码生成、性能分析等任务正在变得越来越智能化。


完整安装与配置

开发环境准备

Node.js 环境配置

移动端开发通常需要 Node.js 环境:

# 检查 Node.js 版本(建议 Node.js 18+)
node --version
# v20.x.x 或更高版本
 
npm --version
# 10.x.x 或更高版本
 
# 推荐使用 nvm 管理 Node.js 版本
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 20
nvm use 20

iOS 开发环境配置

# 检查 Xcode
xcode-select --version
# Xcode 15.0 或更高版本
 
# 安装 Xcode Command Line Tools
xcode-select --install
 
# 同意许可证
sudo xcodebuild -license accept
 
# 安装 CocoaPods(React Native 项目需要)
sudo gem install cocoapods
 
# 或者使用 Homebrew 安装
brew install cocoapods

Android 开发环境配置

# 检查 Java 版本(建议 JDK 17+)
java -version
 
# 安装 Android Studio
# 下载地址:https://developer.android.com/studio
 
# 配置 ANDROID_HOME 环境变量
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
 
# 使用 sdkmanager 安装 SDK 组件
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"

React Native (Expo) 环境配置

# 创建 Expo 项目
npx create-expo-app@latest my-app --template blank-typescript
 
# 进入项目目录
cd my-app
 
# 安装依赖
npm install
 
# 启动开发服务器
npx expo start
 
# 使用特定端口
npx expo start --port 3000
 
# 清除缓存并重启
npx expo start --clear
 
# 预构建原生代码(生成 android/ 和 ios/ 目录)
npx expo prebuild
 
# 运行 iOS 模拟器
npx expo run:ios
 
# 运行 Android 模拟器
npx expo run:android
 
# 构建生产包
npx expo build:android
npx expo build:ios

Flutter 环境配置

# 下载 Flutter SDK
git clone https://github.com/flutter/flutter.git -b stable --depth 1
 
# 添加到 PATH
export PATH="$PATH:/path/to/flutter/bin"
 
# 检查 Flutter 安装
flutter doctor
 
# 创建新项目
flutter create my_app
 
# 进入项目目录
cd my_app
 
# 运行应用
flutter run
flutter run -d "iPhone 15 Pro"
flutter run -d <device-id>
 
# 构建发布版本
flutter build apk --release
flutter build ios --release

Capacitor 环境配置

# 创建 Web 项目
npm create vite@latest my-web-app -- --template vanilla-ts
 
cd my-web-app
npm install
 
# 添加 Capacitor
npm install @capacitor/core @capacitor/cli
 
# 初始化 Capacitor
npx cap init "My App" "com.mycompany.myapp"
 
# 添加平台
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios
 
# 同步到原生项目
npm run build
npx cap sync android
npx cap sync ios

开发工具链配置

// tsconfig.json - TypeScript 配置
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
// package.json - 移动端项目依赖
{
  "name": "mobile-app",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "ios": "npx cap run ios",
    "android": "npx cap run android"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.20.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.2.0",
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "@capacitor/core": "^6.0.0",
    "@capacitor/cli": "^6.0.0",
    "@capacitor/android": "^6.0.0",
    "@capacitor/ios": "^6.0.0"
  }
}

核心概念详解

响应式 Web 设计

移动优先策略

移动优先(Mobile First)是一种设计方法论,主张从最小屏幕开始设计,然后逐步增强到更大屏幕。这种方法的优势在于:

  • 核心内容和功能优先
  • 减少不必要的复杂性
  • 性能更优(移动端优先加载更少代码)
/* ===== 基础样式:移动端优先 ===== */
.container {
  display: flex;
  flex-direction: column;
  padding: 16px;
  gap: 12px;
}
 
.card {
  display: flex;
  flex-direction: column;
  padding: 16px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
 
.card-title {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 8px;
}
 
.card-content {
  font-size: 0.875rem;
  color: #666;
  line-height: 1.5;
}
 
.card-actions {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid #eee;
}
 
/* ===== 平板及以上:增强样式 ===== */
@media (min-width: 768px) {
  .container {
    flex-direction: row;
    flex-wrap: wrap;
    padding: 24px;
    gap: 24px;
  }
  
  .card {
    flex: 1 1 calc(50% - 12px);
    padding: 24px;
  }
  
  .card-title {
    font-size: 1.125rem;
  }
  
  .card-content {
    font-size: 1rem;
  }
}
 
/* ===== 桌面及以上:全功能布局 ===== */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 32px;
  }
  
  .card {
    flex: 1 1 calc(33.333% - 16px);
  }
  
  .card-actions {
    display: flex;
    gap: 12px;
  }
}
 
/* ===== 大屏幕:优化大屏体验 ===== */
@media (min-width: 1440px) {
  .container {
    max-width: 1400px;
  }
  
  .card {
    flex: 1 1 calc(25% - 18px);
  }
}

视口与设备像素比

<!-- 基础视口设置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
 
<!-- 更详细的视口配置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0, viewport-fit=cover">
/* ===== 设备像素比处理 ===== */
/* 高清屏(Retina) */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .icon {
    background-image: url('icon@2x.png');
    background-size: 24px 24px;
  }
  
  .logo {
    background-image: url('logo@2x.png');
    background-size: contain;
  }
}
 
/* 超高清屏(3x) */
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
  .icon {
    background-image: url('icon@3x.png');
  }
}
 
/* ===== 动态视口高度(iOS Safari 适配)===== */
.full-height {
  height: 100dvh;  /* dynamic viewport height */
  min-height: -webkit-fill-available; /* iOS Safari 安全区域 */
}
 
/* 移动端滚动容器 */
.scroll-container {
  height: 100dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch; /* iOS 弹性滚动 */
  overscroll-behavior: contain; /* 防止滚动穿透 */
}
 
/* ===== 安全区域适配 ===== */
.safe-area {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
 
/* 底部导航安全区域 */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding-bottom: env(safe-area-inset-bottom);
  background: white;
}
 
/* 顶部状态栏安全区域 */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding-top: env(safe-area-inset-top);
  background: white;
}

触摸交互优化

/* ===== 触摸反馈 ===== */
.touchable {
  /* 移除 iOS 点击高亮 */
  -webkit-tap-highlight-color: transparent;
  
  /* 触摸优化 */
  touch-action: manipulation; /* 禁用双击缩放 */
  
  /* 防止文字选择 */
  user-select: none;
  -webkit-user-select: none;
  
  /* 触摸反馈动画 */
  transition: transform 100ms ease, opacity 100ms ease;
}
 
.touchable:active {
  transform: scale(0.98);
  opacity: 0.9;
}
 
/* 禁用触摸反馈 */
.no-touch {
  -webkit-tap-highlight-color: auto;
  touch-action: auto;
}
 
/* ===== 长按菜单 ===== */
.context-menu-trigger {
  -webkit-touch-callout: default;  /* 显示默认菜单 */
}
 
.no-callout {
  -webkit-touch-callout: none;  /* 禁用长按菜单 */
}
 
/* ===== 滚动优化 ===== */
.optimized-scroll {
  -webkit-overflow-scrolling: touch; /* iOS 弹性滚动 */
  overflow-scrolling: touch;
  
  /* 滚动平滑 */
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  
  /* 惯性滚动(Android)*/
  -webkit-overflow-scrolling: auto;
  overflow-y: scroll;
  -moz-overflow-scrolling: touch;
}
 
/* ===== 拖拽优化 ===== */
.draggable {
  touch-action: none; /* 允许自由拖拽 */
}
 
/* ===== 边缘手势区域 ===== */
.edge-gesture-zone {
  touch-action: pan-left; /* 允许左滑 */
  touch-action: pan-right; /* 允许右滑 */
}
 
/* ===== 按钮最小触摸区域 ===== */
.mobile-button {
  min-width: 44px;  /* iOS 最小触摸区域 */
  min-height: 44px;
  padding: 12px 24px;
}
 
/* ===== 滑动列表 ===== */
.swipe-list {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  gap: 12px;
  padding: 12px;
}
 
.swipe-list-item {
  flex: 0 0 280px; /* 固定宽度 */
  scroll-snap-align: start;
}

PWA(渐进式 Web 应用)

PWA 核心要素详解

PWA 结合了 Web 应用和原生应用的最佳特性:

优势

  • 无需应用商店审核,即时可更新
  • 跨平台兼容,单一代码库
  • 安装便捷,用户参与度高
  • 后台同步,离线可用
  • 推送通知,用户召回
  • 应用级体验

局限

  • 无法访问部分原生 API
  • 应用商店可见性较低
  • iOS Safari 支持不完整
PWA 技术架构
├── Web App Manifest (应用清单)
│   ├── name / short_name
│   ├── icons (多尺寸)
│   ├── theme_color
│   ├── background_color
│   ├── display (standalone/fullscreen/browser)
│   └── start_url / scope
│
├── Service Worker (服务工作者)
│   ├── 缓存策略
│   │   ├── Cache First
│   │   ├── Network First
│   │   ├── Stale While Revalidate
│   │   └── Network Only
│   │
│   ├── 生命周期
│   │   ├── install
│   │   ├── activate
│   │   └── fetch
│   │
│   └── 后台同步
│       ├── Background Sync
│       └── Periodic Background Sync
│
└── HTTPS (必需条件)

Web App Manifest 完整配置

{
  "name": "我的 PWA 应用",
  "short_name": "应用名",
  "description": "这是一个功能丰富的 PWA 应用",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait-primary",
  "background_color": "#ffffff",
  "theme_color": "#3b82f6",
  "dir": "ltr",
  "lang": "zh-CN",
  
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ],
  
  "categories": ["productivity", "utilities", "lifestyle"],
  
  "shortcuts": [
    {
      "name": "新建任务",
      "short_name": "新建",
      "description": "创建一个新任务",
      "url": "/new-task",
      "icons": [
        {
          "src": "/icons/shortcut-new.png",
          "sizes": "96x96"
        }
      ]
    },
    {
      "name": "查看任务",
      "short_name": "任务",
      "description": "查看所有任务",
      "url": "/tasks"
    }
  ],
  
  "screenshots": [
    {
      "src": "/screenshots/desktop.png",
      "sizes": "1920x1080",
      "type": "image/png",
      "form_factor": "wide",
      "label": "桌面视图"
    },
    {
      "src": "/screenshots/mobile.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow",
      "label": "移动端视图"
    }
  ],
  
  "related_applications": [],
  "prefer_related_applications": false
}

Service Worker 完整实现

// sw.js - Service Worker 主文件
 
// ===== 版本控制和缓存名称 =====
const CACHE_VERSION = 'v1.0.0';
const STATIC_CACHE = `static-cache-${CACHE_VERSION}`;
const DYNAMIC_CACHE = `dynamic-cache-${CACHE_VERSION}`;
const IMAGE_CACHE = `image-cache-${CACHE_VERSION}`;
 
// ===== 需要预缓存的资源 =====
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/offline.html',
  '/manifest.json',
  '/styles/main.css',
  '/scripts/main.js',
  '/images/logo.png',
  '/images/icon-192.png',
  '/images/icon-512.png',
  '/fonts/inter-var.woff2',
];
 
// ===== 缓存大小限制 =====
const MAX_CACHE_ITEMS = 100;
const MAX_IMAGE_CACHE_SIZE = 50;
 
// ===== 安装阶段:预缓存核心资源 =====
self.addEventListener('install', (event) => {
  console.log('[ServiceWorker] Installing...');
  
  event.waitUntil(
    caches.open(STATIC_CACHE)
      .then((cache) => {
        console.log('[ServiceWorker] Precaching static assets');
        return cache.addAll(PRECACHE_URLS);
      })
      .then(() => self.skipWaiting())
  );
});
 
// ===== 激活阶段:清理旧缓存 =====
self.addEventListener('activate', (event) => {
  console.log('[ServiceWorker] Activating...');
  
  event.waitUntil(
    caches.keys()
      .then((cacheNames) => {
        return Promise.all(
          cacheNames
            .filter((cacheName) => {
              // 删除旧版本的缓存
              return cacheName.startsWith('static-cache-') ||
                     cacheName.startsWith('dynamic-cache-') ||
                     cacheName.startsWith('image-cache-');
            })
            .filter((cacheName) => {
              // 保留当前版本的缓存
              return !cacheName.includes(CACHE_VERSION);
            })
            .map((cacheName) => {
              console.log('[ServiceWorker] Removing old cache:', cacheName);
              return caches.delete(cacheName);
            })
        );
      })
      .then(() => self.clients.claim())
  );
});
 
// ===== 请求拦截:缓存策略 =====
self.addEventListener('fetch', (event) => {
  const { request } = event;
  const url = new URL(request.url);
  
  // 只处理同源请求
  if (url.origin !== location.origin) {
    return;
  }
  
  // HTML 页面:网络优先,降级到缓存
  if (request.mode === 'navigate') {
    event.respondWith(networkFirstStrategy(request));
    return;
  }
  
  // 静态资源:缓存优先
  if (isStaticAsset(url)) {
    event.respondWith(cacheFirstStrategy(request));
    return;
  }
  
  // 图片资源:缓存优先,带大小限制
  if (isImage(request)) {
    event.respondWith(imageCacheStrategy(request));
    return;
  }
  
  // API 请求:网络优先
  if (isAPIRequest(url)) {
    event.respondWith(networkFirstStrategy(request));
    return;
  }
  
  // 默认:网络优先
  event.respondWith(networkFirstStrategy(request));
});
 
// ===== 缓存策略实现 =====
 
/**
 * 缓存优先策略
 * 适用于静态资源(CSS、JS、字体等)
 */
async function cacheFirstStrategy(request) {
  const cachedResponse = await caches.match(request);
  
  if (cachedResponse) {
    return cachedResponse;
  }
  
  try {
    const networkResponse = await fetch(request);
    
    if (networkResponse.ok) {
      const cache = await caches.open(STATIC_CACHE);
      cache.put(request, networkResponse.clone());
    }
    
    return networkResponse;
  } catch (error) {
    console.error('[ServiceWorker] Fetch failed:', error);
    return new Response('Network error', { status: 408 });
  }
}
 
/**
 * 网络优先策略
 * 适用于 HTML 页面和 API 请求
 */
async function networkFirstStrategy(request) {
  try {
    const networkResponse = await fetch(request);
    
    if (networkResponse.ok) {
      const cache = await caches.open(DYNAMIC_CACHE);
      cache.put(request, networkResponse.clone());
    }
    
    return networkResponse;
  } catch (error) {
    console.log('[ServiceWorker] Network failed, trying cache');
    const cachedResponse = await caches.match(request);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    // 如果是导航请求,返回离线页面
    if (request.mode === 'navigate') {
      return caches.match('/offline.html');
    }
    
    return new Response('Offline', { status: 503 });
  }
}
 
/**
 * 图片缓存策略(带大小限制)
 */
async function imageCacheStrategy(request) {
  const cachedResponse = await caches.match(request);
  
  if (cachedResponse) {
    return cachedResponse;
  }
  
  try {
    const networkResponse = await fetch(request);
    
    if (networkResponse.ok) {
      const cache = await caches.open(IMAGE_CACHE);
      
      // 清理旧缓存
      trimCache(IMAGE_CACHE, MAX_IMAGE_CACHE_SIZE);
      
      cache.put(request, networkResponse.clone());
    }
    
    return networkResponse;
  } catch (error) {
    // 图片加载失败,返回占位图
    return caches.match('/images/placeholder.png');
  }
}
 
// ===== 辅助函数 =====
 
function isStaticAsset(url) {
  return url.pathname.match(/\.(css|js|woff2?|ttf|eot)$/);
}
 
function isImage(request) {
  return request.destination === 'image';
}
 
function isAPIRequest(url) {
  return url.pathname.startsWith('/api/');
}
 
async function trimCache(cacheName, maxItems) {
  const cache = await caches.open(cacheName);
  const keys = await cache.keys();
  
  if (keys.length > maxItems) {
    await cache.delete(keys[0]);
    trimCache(cacheName, maxItems);
  }
}
 
// ===== 后台同步 =====
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-tasks') {
    event.waitUntil(syncTasks());
  }
});
 
async function syncTasks() {
  // 从 IndexedDB 获取待同步的任务
  const tasks = await getPendingTasks();
  
  for (const task of tasks) {
    try {
      await fetch('/api/tasks', {
        method: 'POST',
        body: JSON.stringify(task),
        headers: { 'Content-Type': 'application/json' },
      });
      
      // 删除已同步的任务
      await removeTask(task.id);
    } catch (error) {
      console.error('[ServiceWorker] Sync failed:', error);
    }
  }
}
 
// ===== 推送通知 =====
self.addEventListener('push', (event) => {
  if (!event.data) return;
  
  const data = event.data.json();
  
  const options = {
    body: data.body,
    icon: '/images/icon-192.png',
    badge: '/images/badge-72.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: data.id,
    },
    actions: [
      { action: 'view', title: '查看' },
      { action: 'dismiss', title: '忽略' },
    ],
  };
  
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});
 
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  
  if (event.action === 'view') {
    event.waitUntil(
      clients.openWindow(`/tasks/${event.notification.data.primaryKey}`)
    );
  }
});

Vite PWA 插件配置

# 安装 Vite PWA 插件
npm install -D vite-plugin-pwa
// vite.config.ts
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
 
export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
      manifest: {
        name: '我的 PWA 应用',
        short_name: '应用',
        description: '功能丰富的 PWA 应用',
        theme_color: '#3b82f6',
        background_color: '#ffffff',
        display: 'standalone',
        orientation: 'portrait',
        scope: '/',
        start_url: '/',
        icons: [
          {
            src: '/pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: '/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
          },
          {
            src: '/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable',
          },
        ],
      },
      workbox: {
        // 全局缓存策略
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        
        // 运行时缓存规则
        runtimeCaching: [
          {
            // Google Fonts 缓存
            urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'google-fonts-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 * 365, // 1年
              },
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
          {
            // 字体文件缓存
            urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'gstatic-fonts-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 * 365, // 1年
              },
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
          {
            // API 请求缓存
            urlPattern: /^https:\/\/api\.example\.com\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24, // 1天
              },
              networkTimeoutSeconds: 10,
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
          {
            // 图片缓存
            urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'image-cache',
              expiration: {
                maxEntries: 60,
                maxAgeSeconds: 60 * 60 * 24 * 30, // 30天
              },
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
        ],
      },
      
      // 开发环境提示
      devOptions: {
        enabled: true,
        type: 'module',
      },
    }),
  ],
});

React Native 深度解析

Expo 开发工作流

# ===== 创建新项目 =====
npx create-expo-app@latest my-app --template blank-typescript
 
# ===== 项目结构 =====
my-app/
├── app/                    # Expo Router 文件
   ├── _layout.tsx        # 根布局
   ├── index.tsx          # 首页
   └── (tabs)/            # 标签页路由组
       ├── _layout.tsx
       ├── home.tsx
       └── profile.tsx

├── src/
   ├── components/        # 组件
   ├── hooks/             # 自定义 Hooks
   ├── services/          # API 服务
   ├── stores/            # 状态管理
   ├── types/             # TypeScript 类型
   └── utils/             # 工具函数

├── assets/                # 静态资源
   ├── fonts/
   └── images/

├── app.json              # Expo 配置
├── package.json
└── tsconfig.json
// app/_layout.tsx - 根布局
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
 
export default function RootLayout() {
  return (
    <>
      <StatusBar style="auto" />
      <Stack>
        <Stack.Screen name="index" options={{ title: '首页' }} />
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      </Stack>
    </>
  );
}
// app/(tabs)/_layout.tsx - 标签页布局
import { Tabs } from 'expo-router';
import { useColorScheme } from 'react-native';
import { Colors } from '@/constants/Colors';
 
export default function TabLayout() {
  const colorScheme = useColorScheme();
  const colors = Colors[colorScheme ?? 'light'];
 
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: colors.tint,
        tabBarInactiveTintColor: colors.tabIconDefault,
        tabBarStyle: {
          backgroundColor: colors.background,
          borderTopWidth: 0,
          elevation: 0,
          height: 60,
          paddingBottom: 8,
          paddingTop: 8,
        },
        headerStyle: {
          backgroundColor: colors.background,
        },
        headerTintColor: colors.text,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: '首页',
          tabBarIcon: ({ color }) => <HomeIcon color={color} />,
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: '我的',
          tabBarIcon: ({ color }) => <ProfileIcon color={color} />,
        }}
      />
    </Tabs>
  );
}
// app/index.tsx - 首页
import { View, Text, StyleSheet, FlatList, TouchableOpacity } from 'react-native';
import { Link } from 'expo-router';
 
interface Item {
  id: string;
  title: string;
  description: string;
}
 
const DATA: Item[] = [
  { id: '1', title: '任务一', description: '这是第一个任务' },
  { id: '2', title: '任务二', description: '这是第二个任务' },
  { id: '3', title: '任务三', description: '这是第三个任务' },
];
 
export default function HomeScreen() {
  const renderItem = ({ item }: { item: Item }) => (
    <TouchableOpacity style={styles.card}>
      <Text style={styles.cardTitle}>{item.title}</Text>
      <Text style={styles.cardDescription}>{item.description}</Text>
    </TouchableOpacity>
  );
 
  return (
    <View style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        contentContainerStyle={styles.list}
        showsVerticalScrollIndicator={false}
      />
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f9fa',
  },
  list: {
    padding: 16,
  },
  card: {
    backgroundColor: 'white',
    padding: 20,
    marginBottom: 12,
    borderRadius: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  cardTitle: {
    fontSize: 18,
    fontWeight: '600',
    marginBottom: 8,
    color: '#1a1a1a',
  },
  cardDescription: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
  },
});

React Navigation 完整配置

# 安装 React Navigation
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs
npm install react-native-screens react-native-safe-area-context
// navigation/AppNavigator.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Text, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
 
// 导入屏幕
import HomeScreen from '../screens/HomeScreen';
import ProfileScreen from '../screens/ProfileScreen';
import SettingsScreen from '../screens/SettingsScreen';
import DetailScreen from '../screens/DetailScreen';
 
// 类型定义
export type RootStackParamList = {
  Main: undefined;
  Detail: { id: string; title: string };
  Modal: { mode: 'edit' | 'create' };
};
 
export type MainTabParamList = {
  Home: undefined;
  Profile: undefined;
  Settings: undefined;
};
 
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<MainTabParamList>();
 
// 标签页组件
function MainTabs() {
  return (
    <Tab.Navigator
      screenOptions={{
        tabBarActiveTintColor: '#3b82f6',
        tabBarInactiveTintColor: '#94a3b8',
        tabBarStyle: {
          backgroundColor: 'white',
          borderTopWidth: 0,
          elevation: 10,
          shadowColor: '#000',
          shadowOffset: { width: 0, height: -2 },
          shadowOpacity: 0.1,
          shadowRadius: 8,
        },
        headerShown: false,
      }}
    >
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: '首页',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tab.Screen
        name="Profile"
        component={ProfileScreen}
        options={{
          title: '我的',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
      <Tab.Screen
        name="Settings"
        component={SettingsScreen}
        options={{
          title: '设置',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="settings" size={size} color={color} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}
 
// 根导航
export default function AppNavigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{
          headerStyle: {
            backgroundColor: 'white',
          },
          headerTintColor: '#1a1a1a',
          headerTitleStyle: {
            fontWeight: '600',
          },
          headerShadowVisible: false,
        }}
      >
        <Stack.Screen
          name="Main"
          component={MainTabs}
          options={{ headerShown: false }}
        />
        <Stack.Screen
          name="Detail"
          component={DetailScreen}
          options={({ route }) => ({
            title: route.params.title,
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Flutter 核心概念

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
 
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}
 
class HomePage extends StatefulWidget {
  const HomePage({super.key});
 
  @override
  State<HomePage> createState() => _HomePageState();
}
 
class _HomePageState extends State<HomePage> {
  final List<Map<String, String>> _items = [
    {'id': '1', 'title': '任务一', 'desc': '这是第一个任务'},
    {'id': '2', 'title': '任务二', 'desc': '这是第二个任务'},
    {'id': '3', 'title': '任务三', 'desc': '这是第三个任务'},
  ];
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
        centerTitle: true,
        elevation: 0,
      ),
      body: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: _items.length,
        itemBuilder: (context, index) {
          final item = _items[index];
          return Card(
            margin: const EdgeInsets.only(bottom: 12),
            elevation: 2,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            child: InkWell(
              onTap: () => _navigateToDetail(item),
              borderRadius: BorderRadius.circular(12),
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      item['title']!,
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      item['desc']!,
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: const Icon(Icons.add),
      ),
    );
  }
 
  void _navigateToDetail(Map<String, String> item) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DetailPage(
          id: item['id']!,
          title: item['title']!,
        ),
      ),
    );
  }
 
  void _addItem() {
    // 添加新项目
  }
}
 
class DetailPage extends StatelessWidget {
  final String id;
  final String title;
 
  const DetailPage({
    super.key,
    required this.id,
    required this.title,
  });
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: const Center(
        child: Text('详情页内容'),
      ),
    );
  }
}

常用命令与操作

React Native / Expo 命令

# ===== 项目创建 =====
npx create-expo-app@latest my-app
npx create-expo-app@latest my-app --template blank-typescript
 
# ===== 开发服务器 =====
npx expo start
npx expo start --offline          # 离线模式
npx expo start --clear            # 清除缓存
npx expo start --port 3000        # 指定端口
npx expo start --tunnel           # 隧道模式
 
# ===== 平台运行 =====
npx expo run:ios                  # iOS 模拟器
npx expo run:android              # Android 模拟器
npx expo run:ios --device         # iOS 真机
npx expo run:android --device      # Android 真机
 
# ===== 原生构建 =====
npx expo prebuild                # 生成原生项目
npx expo prebuild --clean         # 清理后重新生成
 
# ===== 构建发布 =====
eas build -p ios                  # iOS 构建
eas build -p android             # Android 构建
eas build -p android --profile preview  # 预览构建
 
# ===== 应用发布 =====
eas submit -p ios                 # 提交到 App Store
eas submit -p android             # 提交到 Google Play
 
# ===== 开发工具 =====
npx expo install                  # 交互式安装依赖
npx expo doctor                   # 检查项目健康状态
npx expo update                   # 更新 Expo SDK

Flutter 命令

# ===== 项目创建 =====
flutter create my_app
flutter create --org com.example my_app
 
# ===== 开发运行 =====
flutter run
flutter run -d "iPhone 15 Pro"    # 指定设备
flutter run -d emulator-5554       # 指定模拟器
flutter run --release             # 发布模式
 
# ===== 构建发布 =====
flutter build apk --release       # Android APK
flutter build apk --debug          # Android Debug APK
flutter build ios --release        # iOS 发布
flutter build ios --simulator --no-codesign  # iOS 模拟器
 
# ===== 工具命令 =====
flutter doctor                     # 检查环境
flutter pub get                   # 获取依赖
flutter pub upgrade               # 升级依赖
flutter clean                     # 清理构建

Capacitor 命令

# ===== 初始化 =====
npx cap init "App Name" "com.example.app"
 
# ===== 添加平台 =====
npx cap add ios
npx cap add android
 
# ===== 同步 =====
npx cap sync ios
npx cap sync android
npx cap sync                     # 同步所有平台
 
# ===== 打开原生项目 =====
npx cap open ios
npx cap open android
 
# ===== 其他命令 =====
npx cap copy                     # 复制 Web 资源
npx cap update                   # 更新 Capacitor
npx cap ls                       # 列出已添加的平台
npx cap doctor                   # 检查环境

高级配置与技巧

移动端性能优化

/* ===== 渲染优化 ===== */
/* 启用 GPU 加速 */
.gpu-accelerated {
  transform: translateZ(0);
  will-change: transform;
}
 
/* 减少重排 */
.no-layout {
  contain: layout style;
}
 
/* ===== 图片优化 ===== */
.responsive-image {
  max-width: 100%;
  height: auto;
  loading: lazy;  /* 原生懒加载 */
}
 
/* 使用 srcset */
<img 
  src="image-400.jpg"
  srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
  alt="Responsive image"
/>

移动端安全区域适配

/* ===== iPhone X 及以上适配 ===== */
:root {
  --safe-area-inset-top: env(safe-area-inset-top);
  --safe-area-inset-bottom: env(safe-area-inset-bottom);
  --safe-area-inset-left: env(safe-area-inset-left);
  --safe-area-inset-right: env(safe-area-inset-right);
}
 
/* 应用到布局 */
.safe-area-top {
  padding-top: var(--safe-area-inset-top);
}
 
.safe-area-bottom {
  padding-bottom: var(--safe-area-inset-bottom);
}
 
/* 固定底部导航 */
.fixed-bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding-bottom: env(safe-area-inset-bottom);
}

移动端手势处理

// 移动端手势库配置
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { StyleSheet } from 'react-native';
 
export default function App() {
  return (
    <GestureHandlerRootView style={styles.container}>
      {/* 应用内容 */}
    </GestureHandlerRootView>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

移动端原生能力访问

// 使用 expo-camera 访问相机
import { CameraView, useCameraPermissions } from 'expo-camera';
 
export function CameraComponent() {
  const [permission, requestPermission] = useCameraPermissions();
 
  if (!permission) {
    return <View />;
  }
 
  if (!permission.granted) {
    return (
      <Button title="授权相机" onPress={requestPermission} />
    );
  }
 
  return (
    <CameraView
      style={styles.camera}
      facing="back"
      onBarcodeScanned={handleBarcodeScanned}
    />
  );
}
 
// 使用 expo-location 获取位置
import * as Location from 'expo-location';
 
export async function getCurrentLocation() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  
  if (status !== 'granted') {
    throw new Error('位置权限未授权');
  }
 
  const location = await Location.getCurrentPositionAsync({});
  return location;
}
 
// 使用 expo-file-system 处理文件
import * as FileSystem from 'expo-file-system';
 
export async function downloadFile(url: string, filename: string) {
  const result = await FileSystem.downloadAsync(url, 
    FileSystem.documentDirectory + filename
  );
  return result.uri;
}

与同类技术对比

框架对比表

维度React NativeFlutterCapacitorTauri
语言TypeScriptDartTypeScriptRust + Web
渲染方式原生组件Skia 自绘WebViewWebView
性能接近原生极高性能中等高性能
包大小~30MB~10MB~15MB~3MB
生态丰富度极丰富丰富依赖 Web 生态快速增长
学习曲线中等陡峭平缓中等
热更新支持支持(有限)支持需审核
原生能力完整完整完整需要插件
适用场景商业 App高性能 AppWeb 迁移桌面+移动
社区活跃度非常高中等快速增长

选型决策树

需要构建移动应用?
│
├─ 已有 Web 应用?
│   ├─ 是 → Capacitor(最低成本,Web 技术复用)
│   └─ 否 → 继续判断
│
├─ 追求原生性能?
│   ├─ React 团队 → React Native + Expo(推荐)
│   └─ 性能极致追求 → Flutter
│
├─ 需要同时覆盖桌面 + 移动?
│   └─ 是 → Tauri(Rust 性能,小体积)
│
├─ 需要应用商店分发?
│   ├─ 是 → React Native / Flutter
│   └─ 否 → PWA(最低成本)
│
└─ 纯内容展示型应用?
    └─ 是 → PWA / 响应式 Web

技术选型详细指南

React Native + Expo 适用场景

  • 需要快速开发的商业应用
  • 已有 React 团队
  • 需要原生功能和性能
  • 需要热更新能力

Flutter 适用场景

  • 需要极致性能的自定义 UI
  • 需要复杂动画和手势
  • 游戏或图形密集型应用
  • 需要同时覆盖移动和桌面

Capacitor 适用场景

  • 已有 Web 应用需要移动化
  • 团队擅长 Web 技术
  • 需要快速进入应用商店
  • 原型验证阶段

PWA 适用场景

  • 不需要应用商店分发
  • 需要最大覆盖面
  • 预算有限
  • 快速迭代更新

常见问题与解决方案

问题1:移动端 300ms 点击延迟

原因:移动端浏览器默认在 touchend 后等待 300ms 检测是否为双击缩放

解决

<!-- 方法1:禁用视口缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
 
<!-- 方法2:使用 touch-action CSS 属性 -->
<style>
  .no-zoom {
    touch-action: manipulation;
  }
</style>

问题2:iOS 橡皮筋滚动问题

解决

/* 方法1:使用 overscroll-behavior */
.scroll-container {
  overscroll-behavior: contain;
}
 
/* 方法2:禁用弹性滚动 */
.no-bounce {
  -webkit-overflow-scrolling: auto;
  overflow-y: scroll;
}

问题3:移动端输入框被键盘遮挡

解决

// 监听键盘事件
const input = document.querySelector('input');
 
input.addEventListener('focus', () => {
  setTimeout(() => {
    input.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }, 300);
});

问题4:React Native 真机调试连接失败

解决

# 确保设备和电脑在同一网络
# 检查防火墙设置
# 使用 IP 地址而非 localhost
# 清除 Metro 缓存
npx expo start --clear

问题5:Android 设备无法安装 APK

解决

# 检查是否启用了"未知来源"安装
# 在设置 → 安全 → 未知来源 中启用
 
# 检查签名是否正确
# Release APK 必须使用正确的签名
 
# 清除安装缓存
adb shell pm clear com.yourapp
adb install -r yourapp.apk

问题6:Flutter 热重载不生效

解决

# 方法1:重启热重载
# 在终端输入 'r'
 
# 方法2:完全重新加载
# 输入 'R' (大写)
 
# 方法3:检查是否有错误
# 查看控制台输出

实战项目示例

示例1:PWA 离线任务管理应用

// 完整实现参考本文档 Service Worker 部分
 
// 主要功能:
// 1. 离线缓存核心资源
// 2. 后台同步任务数据
// 3. 推送通知提醒
// 4. 应用安装提示

示例2:React Native 电商应用

// 主要功能:
// 1. Expo Router 页面导航
// 2. React Navigation 标签页
// 3. FlatList 高性能列表
// 4. 本地存储持久化
// 5. 下拉刷新与上拉加载

示例3:Flutter 博客阅读器

// 主要功能:
// 1. Material Design 3 设计
// 2. Provider 状态管理
// 3. JSON 数据解析
// 4. 图片缓存
// 5. 主题切换

附录:移动端开发资源

常用 API 速查

// ===== 触摸事件 =====
element.addEventListener('touchstart', handleTouchStart, { passive: false });
element.addEventListener('touchmove', handleTouchMove, { passive: false });
element.addEventListener('touchend', handleTouchEnd, { passive: false });
 
// ===== 设备方向 =====
window.addEventListener('deviceorientation', (event) => {
  console.log('gamma:', event.gamma); // 左右倾斜
  console.log('beta:', event.beta);   // 前后倾斜
});
 
// ===== 电池状态 =====
navigator.getBattery().then(battery => {
  console.log('charging:', battery.charging);
  console.log('level:', battery.level);
});
 
// ===== 网络状态 =====
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
 
// ===== 振动反馈 =====
navigator.vibrate(100); // 振动 100ms
navigator.vibrate([100, 50, 100]); // 振动模式

移动端调试技巧

# ===== iOS Safari 调试 =====
# 1. iPhone: 设置 → Safari → 高级 → Web 检查器: 开
# 2. Mac: Safari → 开发 → [设备名] → [页面]
 
# ===== Android Chrome 调试 =====
# 1. 手机: 设置 → 关于手机 → 连续点击"版本号"7次
# 2. 手机: 设置 → 开发者选项 → USB 调试: 开
# 3. USB 连接
# 4. Chrome: chrome://inspect
 
# ===== 真机调试(React Native)=====
# Android
adb reverse tcp:8081 tcp:8081
 
# iOS (需要同一网络)
# 在 Safari 开发菜单中选择设备

性能监控工具

// ===== Web Vitals =====
import { getCLS, getFID, getLCP } from 'web-vitals';
 
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
 
// ===== 自定义性能指标 =====
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log('Performance:', entry.name, entry.duration);
  });
});
 
observer.observe({ type: 'longtask', buffered: true });

附录:跨平台框架资源

官方文档链接

社区资源

资源类型链接
React Native 中文网https://reactnative.cn
Flutter 中文网https://flutter.cn
Expo 社区https://expo.dev/community

移动端与跨平台测试矩阵

| 设备类型 | 品牌 | 屏幕尺寸 | 分辨率 | 系统版本 | 测试重点 |
|----------|------|----------|--------|----------|----------|
| 手机 | iPhone 15 Pro | 6.1" | 1179x2556 | iOS 17+ | 主流旗舰 |
| 手机 | iPhone SE | 4.7" | 750x1334 | iOS 17+ | 小屏适配 |
| 手机 | Samsung S24 Ultra | 6.8" | 1440x3088 | Android 14 | 旗舰 Android |
| 手机 | Google Pixel 8 | 6.2" | 1080x2400 | Android 14 | 原生体验 |
| 手机 | 小米 14 | 6.4" | 1200x2670 | Android 14 | 国产旗舰 |
| 平板 | iPad Pro 12.9" | 12.9" | 2048x2732 | iPadOS 17+ | 大屏布局 |
| 平板 | Samsung Tab S9 | 12.4" | 1752x2800 | Android 14 | 平板适配 |
| 折叠 | Samsung Z Fold 5 | 7.6" | 1812x2176 | Android 14 | 折叠适配 |

附录:React Native / Expo 常见问题速查

环境问题

# ===== Node.js 版本问题 =====
# React Native 0.73+ 推荐 Node.js 18+
# 使用 nvm 管理版本
nvm install 20
nvm use 20
 
# ===== Expo SDK 版本 =====
# 查看可升级版本
npx expo install --check
 
# 升级 Expo SDK
npx expo upgrade
 
# ===== iOS 构建问题 =====
# 清理 Xcode 缓存
rm -rf ~/Library/Developer/Xcode/DerivedData
 
# 更新 CocoaPods
cd ios && pod deintegrate && pod install
 
# ===== Android 构建问题 =====
# 清理 Gradle 缓存
cd android && ./gradlew clean
 
# 重新生成原生代码
npx expo prebuild --clean

运行时问题

// ===== 组件卸载后更新状态 =====
const [data, setData] = useState(null);
 
// 使用 isMounted 检查
useEffect(() => {
  let isMounted = true;
  
  fetchData().then(result => {
    if (isMounted) {
      setData(result);
    }
  });
  
  return () => {
    isMounted = false;
  };
}, []);
 
// 或者使用 AbortController
useEffect(() => {
  const controller = new AbortController();
  
  fetchData({ signal: controller.signal })
    .then(setData);
  
  return () => controller.abort();
}, []);

性能问题

// ===== 列表性能优化 =====
<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={item => item.id}
  // 关键优化
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  windowSize={5}
  initialNumToRender={10}
  getItemLayout={(_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>
 
// ===== 图片优化 =====
import { Image } from 'react-native';
 
<Image
  source={{ uri: 'https://...' }}
  style={styles.image}
  // 缓存控制
  cachePolicy="disk"
  // 占位图
  placeholder={{ uri: 'placeholder.png' }}
  loadingIndicatorSource={{ uri: 'loading.gif' }}
/>

附录:Flutter 常见问题速查

环境配置问题

# ===== Flutter 版本管理 =====
flutter upgrade              # 升级到最新稳定版
flutter downgrade <version>   # 降级到指定版本
flutter version             # 查看当前版本
 
# ===== Android SDK 问题 =====
flutter doctor -v            # 详细检查
flutter config --android-sdk /path/to/android/sdk
 
# ===== iOS 构建问题 =====
cd ios && pod deintegrate
cd ios && pod install
cd ..
flutter build ios

常见运行时问题

// ===== 状态管理问题 =====
// 使用 setState 后立即读取状态
setState(() {
  _items.add(newItem);
});
 
// 读取时仍需要使用 setState 回调或 Future
setState(() {});
await Future.microtask(() {}); // 确保状态更新
 
// ===== 网络请求问题 =====
// 添加超时
final response = await http
  .get(Uri.parse(url))
  .timeout(Duration(seconds: 30));
 
// 处理错误
try {
  final response = await http.get(Uri.parse(url));
} on SocketException {
  // 网络不可用
} on TimeoutException {
  // 请求超时
}

TIP

移动端推荐策略:对于大多数 vibecoding 项目,PWA 是成本最低、覆盖面最广的选择。对于需要应用商店分发的应用,React Native + Expo 是最佳平衡点。