@[toc]
为什么改个小东西就要重构建?先看痛点
现实中常见的几种烦恼:
- 页面改一点,热刷新失效或导致状态丢失,需要重装 App。
- 真机调试慢:每次改完都得等 Gradle/Xcode 编译,效率低下。
- 错误堆栈难看(混淆/无 sourcemap),定位费劲。
- QA / PM 想要灰度发布但你不敢随意用热更(怕回滚/安全)。
- Metro 缓存或 watchman 配置不当引起热重载不稳定。
本文把这些事儿拆成几类问题:工具链(Flipper)、热更(CodePush)、Metro 与缓存、错误定位(sourcemap + 报错系统)和自动化脚本。每部分都有可运行示例或脚本片段。
一、Flipper 全套调试技巧(神器级提升)
Flipper 是 React Native 官方推荐的调试平台,集成了网络、布局、数据库、堆快照、React DevTools、Hermes profiler 等插件。掌握它能极大提升调试效率。
快速上手(安装与集成)
- 安装桌面端:下载 Flipper 应用(Windows / macOS / Linux)并安装。
- 在 RN 项目中确保 iOS/Android 都启用了 Flipper(示例代码见下)。
iOS(Podfile 示例)
在 ios/Podfile 通常会看到类似:
ruby
use_flipper!({ 'Flipper' => '0.125.0' }) # 版本以你项目要求为准
# 或者更稳妥:use_flipper!()
然后:
bash
cd ios && pod install
(如果你在 CI 或 Release 想关闭 Flipper,请按条件只在 Debug 构建加入 Flipper)
Android(MainApplication.java / build.gradle)
在 android/app/build.gradle 与 MainApplication 中按官方示例启用 Flipper。常见坑:release 构建去掉 Flipper 的依赖;如果使用 Hermes,确认 Flipper Hermes 插件兼容。
注意:如果遇到 Flipper 报错,先尝试升级 Flipper 到和 React Native 兼容的版本或在 debugOnly 配置中排除冲突依赖。
推荐 Flipper 插件与用法(实战)
- React DevTools:检查组件树、props/state、hooks。
- Layout Inspector:实时看到 view Hierarchy,能帮你定位布局重叠/透明度问题。
- Network:监控 fetch / XHR / GraphQL 请求、查看请求体/响应体、重发请求。非常适合接口联调。
- Databases(如 Realm/SQLite):能直接查看本地 DB 的表与数据。
- Hermes Debugger(如果你启用了 Hermes):查看 GC、堆、耗时函数。
- Crash Reporter(Sentry/Crashlytics 插件集成):快速在本地复现并抓取原生崩溃信息。
实战技巧
- 用 Flipper 的 Network 面板重放接口失败场景(复制 request,直接修改 body 重试)。
- 当某页面出现内存泄漏,用 Hermes Profiler 做一次帧分析并取 heap snapshot。
- 在列表卡顿时结合 Layout Inspector + FPS 插件查看绘制时间和 JS 阻塞时间。
- 通过 React DevTools 观察组件是否频繁 rerender(props / state 改变导致),配合
why-did-you-render排查重复渲染。
二、CodePush(热更)配合灰度发布 ------ 让小变更不用重装包
CodePush 能把 JS bundle 与资源推到用户端(注意:不能热更原生代码)。配合"灰度发布(rollout)"能把更新逐步推给一定比例用户,降低风险。
安装与基本用法(概要)
- 安装 SDK:
bash
yarn add react-native-code-push
npx pod-install
- JS 端调用(示例):
js
import codePush from "react-native-code-push";
const options = { checkFrequency: codePush.CheckFrequency.MANUAL };
// 在 App 启动时手动检测
async function checkUpdate() {
const update = await codePush.checkForUpdate();
if (update) {
await codePush.sync({
installMode: codePush.InstallMode.ON_NEXT_RESTART,
updateDialog: true
});
}
}
- 发布命令(App Center / CodePush CLI)------示例(伪命令,按你组织所有权替换):
bash
appcenter codepush release-react -a <owner>/<app-android> -d Staging --description "fix xxx"
# 或者带 roll out
appcenter codepush release-react -a <owner>/<app-android> -d Production --rollout 0.2
--rollout 0.2 表示先推 20% 的用户。你也可用 --disabled 或 --mandatory.
实战建议
- 把 CodePush 分成 dev/staging/prod 三套 deployment keys(避免误发)。
- 依赖关键的原生更新必须走 app store(禁止把 native 修改放 CodePush)。
- 发布前在内部群发给 QA 做灰度验证,再放更大比例。
- 设计好回退机制:在 app 中保存上次成功 bundle 的版本,当当前 bundle 崩溃过多(Crash Rate 超阈值)自动回退到上一版(CodePush 支持回滚)。
三、Metro 缓存与启动优化(少重启,多热更新)
Metro 配置优化与缓存策略能显著提升开发体验。
常见命令(开发必备)
bash
# 普通启动
yarn start
# 强制清缓存
yarn start --reset-cache
# watchman 清理(当热加载失灵时)
watchman watch-del-all
rm -rf $TMPDIR/react-*
metro.config.js 优化示例(可直接复制)
metro.config.js(示例):
js
const { getDefaultConfig } = require("metro-config");
module.exports = (async () => {
const config = await getDefaultConfig();
return {
...config,
transformer: {
...config.transformer,
// inlineRequires 把某些模块在首次使用时才 require,能减小 cold start CPU 开销
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
// 可以自定义 babelTransformerPath 引入更快或定制的 transform
},
resolver: {
...config.resolver,
// 在 monorepo 场景,常需要增加 watchFolders
// watchFolders: [path.resolve(__dirname, '../packages')],
},
maxWorkers: require('os').cpus().length // 使用所有 CPU
};
})();
常见调优点
inlineRequires:减少首屏解析开销。maxWorkers:根据机器 CPU 合理设置,不要超过 CPU 数量。- watchman:macOS 下强烈建议安装并运行 watchman(npm install -g watchman),配合
watchman watch-del-all来清理。 - 如果你使用 monorepo,确保 metro resolver 配置正确(
watchFolders/blockList)以避免重复加载 node_modules。
四、快速错误定位技巧(sourcemap + 日志 + 本地复现)
错误定位分两步:捕获 (Sentry/Crashlytics)和本地复现/调试(Flipper + source maps)。
1. 在生产环境抓 error(Sentry 示例)
安装 Sentry:
bash
yarn add @sentry/react-native
npx @sentry/wizard -i reactNative -p ios android
初始化:
js
import * as Sentry from '@sentry/react-native';
Sentry.init({ dsn: 'https://<key>@sentry.io/<project>', enableNative: true });
打包时上传 sourcemap(重要!):
bash
# 生成 bundle 并上传 source maps 到 Sentry(示例脚本)
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --sourcemap-output ./android-output/index.android.bundle.map
sentry-cli upload-sourcemaps ./android-output --ext map --ext bundle --rewrite --dist 1.0.0
Sentry 会把报错 stacktrace 映射回 JS 源码位置,定位效率大幅提升。
2. 上传 source map 的 CI 脚本(示例:GitHub Actions 片段)
.github/workflows/sourcemap-upload.yml
yaml
name: Upload Sourcemaps
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install
run: yarn install
- name: Bundle Android
run: |
npx react-native bundle --platform android --dev false --entry-file index.js \
--bundle-output ./android-output/index.android.bundle \
--sourcemap-output ./android-output/index.android.bundle.map
- name: Upload to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
curl -sL https://sentry.io/get-cli/ | bash
./sentry-cli releases new $GITHUB_SHA
./sentry-cli releases files $GITHUB_SHA upload-sourcemaps ./android-output --rewrite
3. 本地快速定位技巧
- 在本地用 Flipper + Network 面板重放请求,看是否为后端问题。
- 在本地用 Sentry 的
captureMessage/captureException打点关键位置以便线上复现时定位。 - 使用
console.tron/ Reactotron / Flipper 的React DevTools实时查看组件状态。
五、自动化调试脚本(一键启动 + 热更 + 清缓存)
把常用组合封成 NPM scripts,做到"一键开发"。
package.json 示例(拷贝到你项目):
json
{
"scripts": {
"start": "react-native start",
"start:reset": "watchman watch-del-all && rm -rf $TMPDIR/react-* && react-native start --reset-cache",
"android": "react-native run-android",
"ios": "react-native run-ios",
"open:flipper": "open -a Flipper",
"bundle:android": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./android-output/index.android.bundle --sourcemap-output ./android-output/index.android.bundle.map",
"codepush:staging": "appcenter codepush release-react -a owner/app-android -d Staging",
"codepush:rollout-prod": "appcenter codepush release-react -a owner/app-android -d Production --rollout 0.1",
"sentry:upload": "node scripts/upload-sourcemap-to-sentry.js"
}
}
scripts/upload-sourcemap-to-sentry.js(示例)
js
const { execSync } = require('child_process');
const sha = process.env.GITHUB_SHA || 'local'
execSync(`npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./android-output/index.android.bundle --sourcemap-output ./android-output/index.android.bundle.map`, { stdio: 'inherit' });
execSync(`sentry-cli releases new ${sha}`, { stdio: 'inherit' });
execSync(`sentry-cli releases files ${sha} upload-sourcemaps ./android-output --rewrite`, { stdio: 'inherit' });
console.log('Uploaded sourcemaps to Sentry for release', sha);
这样 QA / 开发人员只需运行
yarn start:reset或yarn android或yarn codepush:staging就能进入完整工作流。
六、真实场景下的优化与流程建议(团队协作级别)
下面是能显著提升团队效率的流程建议(落地可执行):
-
开发环境尽量启热重载 + Flipper
把 Flipper 配到 debug 构建,开发时始终打开 Flipper,网络与 DB 一看便知。
-
预发用 CodePush 做灰度
先在 Staging 环境把更新给内部 QA 测试,满意后在 Production 做滚动 10% → 50% → 100%。
-
CI 自动打包并上传 source map
每次 release 都让 CI 生成 bundle、上传 sourcemap 并(如果需要)自动执行 codepush release。
-
错误阈值自动回滚
设置 CodePush 回滚策略或在 App 中实现"崩溃率检测并回退"的策略(若某版触发过多崩溃,自动从 CodePush 回滚到上一个稳定版本)。
-
本地 devbox 规则
团队约定本地机器安装 watchman、合理配置 maxWorkers(与 CPU 绑定)、并提供
yarn dev-setup脚本一键准备开发环境。
七、常见问题与解决办法(FAQ)
Q: Flipper 导致 debug 构建失败怎么办?
A: 临时在 Podfile / Gradle 中只在 Debug 环境启用 Flipper,或者降级 Flipper 版本直到和 RN 版本兼容;release 构建一定要剔除 Flipper 依赖。
Q: CodePush 发布出问题如何回退?
A: 直接使用 appcenter codepush rollback -a owner/app-android -d Production 或在发布时指定 --disabled 做灰度先关。
Q: Metro 热加载无效?
A: 先执行 watchman watch-del-all && rm -rf $TMPDIR/react-* && yarn start --reset-cache,并检查是否有重复 node_modules 导致缓存冲突(monorepo 常见)。
八、实战小结(你可以立刻做的 5 件事)
- 安装并用 Flipper 观察一个接口调用的整个链路(Network + DB + React DevTools)。
- 把
metro.config.js设置inlineRequires: true,观察 cold start 改善。 - 用
yarn start:reset加入到团队的 README,成为约定俗成的开发入口。 - 配置 CodePush Staging,并尝试一次小灰度(rollout 10%),验证回滚流程。
- 在 CI 中加入 bundle + sourcemap 上传(Sentry),确保线上错误能被映射到源码。