移动端与跨平台开发完全指南
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
技术选型决策框架
选择移动端技术需要综合考虑以下因素:
| 考虑维度 | 说明 | 权重 |
|---|---|---|
| 团队技能 | 现有技术栈和团队经验 | 高 |
| 应用类型 | 游戏、电商、工具、企业应用等 | 高 |
| 性能要求 | 是否需要极致性能 | 中 |
| 发布渠道 | 应用商店、独立分发 | 中 |
| 开发周期 | 时间预算 | 中 |
| 长期维护 | 预期应用生命周期 | 中 |
| 生态成熟度 | 第三方库和社区支持 | 低 |
本文档的目标读者
本文档旨在帮助开发者:
- 理解移动端 Web 开发的核心技术:掌握响应式设计、PWA、移动端优化等技能
- 选择合适的跨平台方案:根据项目需求选择 React Native、Flutter、Capacitor 等框架
- 掌握移动端调试技能:使用现代开发工具高效调试移动应用
- 实施性能优化策略:确保应用在各种设备上流畅运行
移动端开发的技术趋势
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 20iOS 开发环境配置
# 检查 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 cocoapodsAndroid 开发环境配置
# 检查 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:iosFlutter 环境配置
# 下载 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 --releaseCapacitor 环境配置
# 创建 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 SDKFlutter 命令
# ===== 项目创建 =====
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 Native | Flutter | Capacitor | Tauri |
|---|---|---|---|---|
| 语言 | TypeScript | Dart | TypeScript | Rust + Web |
| 渲染方式 | 原生组件 | Skia 自绘 | WebView | WebView |
| 性能 | 接近原生 | 极高性能 | 中等 | 高性能 |
| 包大小 | ~30MB | ~10MB | ~15MB | ~3MB |
| 生态丰富度 | 极丰富 | 丰富 | 依赖 Web 生态 | 快速增长 |
| 学习曲线 | 中等 | 陡峭 | 平缓 | 中等 |
| 热更新 | 支持 | 支持(有限) | 支持 | 需审核 |
| 原生能力 | 完整 | 完整 | 完整 | 需要插件 |
| 适用场景 | 商业 App | 高性能 App | Web 迁移 | 桌面+移动 |
| 社区活跃度 | 非常高 | 高 | 中等 | 快速增长 |
选型决策树
需要构建移动应用?
│
├─ 已有 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.dev/docs/getting-started |
| Expo | https://docs.expo.dev |
| Flutter | https://docs.flutter.dev |
| Capacitor | https://capacitorjs.com/docs |
| Tauri | https://tauri.app/zh-cn/v1/guides/ |
社区资源
| 资源类型 | 链接 |
|---|---|
| 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 是最佳平衡点。