1. 项目概述:一次真实的 Hermes 升级翻车现场与复盘
手欠升级了一下 Hermes,失败后总结了几点升级的经验——这句话听起来像极了某天凌晨三点的 Slack 频道里,一位前端工程师发完git push后默默撤回消息、转而贴出的一行自嘲。但背后藏着的,是 React Native 生态中一个被低估却高频踩坑的“静默雷区”:Hermes 引擎的版本跃迁。Hermes 不是普通依赖,它是 RN 应用的 JS 执行层底座,升级它,相当于给汽车换发动机——表面只是拧几颗螺丝,实则牵动点火时序、油路匹配、ECU 固件兼容性。我最近在维护一个已上线两年的 RN 项目(RN 版本 0.71.8,Hermes 0.12.0),因需接入新版 React DevTools 的性能分析能力,决定将 Hermes 升至 0.14.0。结果构建直接报错:undefined is not an object (evaluating 'global.__reanimatedWorkletInit'),调试器连不上,热重载失效,首屏白屏长达 8 秒。这不是配置漏写或缓存没清的问题,而是 Hermes 0.14 引入的字节码格式不向下兼容+全局对象挂载策略变更+Polyfill 行为收敛三重叠加导致的连锁崩溃。这次失败让我意识到:Hermes 升级从来不是yarn add hermes-engine就能收工的事,它是一场需要前置测绘、分段验证、灰度放量的系统工程。本文不讲抽象原理,只说我在真实项目中逐行改代码、逐个测模块、逐台设备验效果后沉淀下来的硬核经验。适合所有正在用 RN 且尚未升级到 Hermes 0.13+ 的团队,尤其适合那些把“升级”当成npm update一键操作的开发者。你不需要懂字节码编译器,但必须知道:为什么console.log在新 Hermes 里突然不打印了?为什么 Reanimated 的 worklet 会集体失联?为什么 Metro 打包出来的.hbc文件体积反而变小了却更慢了?这些答案,全在下面的真实操作链路里。
2. Hermes 升级的本质:不是换包,而是切换 JS 运行时契约
2.1 为什么说 Hermes 升级是“运行时契约变更”,而非普通依赖更新?
很多开发者误以为 Hermes 是个“可插拔 JS 引擎”,只要版本号对得上 RN 官方文档的兼容矩阵表,就能平滑替换。这是最大的认知偏差。Hermes 的每次大版本升级(如 0.12 → 0.13 → 0.14),本质是JS 运行时环境契约的重新定义。这个契约包含三个不可分割的层面:
字节码 ABI 层:Hermes 编译器生成的
.hbc字节码文件,其指令集、寄存器映射、调用栈结构在不同版本间不保证二进制兼容。0.14 的字节码无法被 0.12 的运行时加载,反之亦然。这就像 Windows 10 的.exe文件无法在 DOS 下运行——不是功能缺失,是底层指令根本无法识别。全局对象挂载层:Hermes 通过
global对象向 JS 暴露原生能力(如global.HermesInternal、global.__DEV__)。0.13 起,Hermes 开始严格遵循 ECMAScript 规范,移除了部分非标准挂载点。例如,旧版中global.__reanimatedWorkletInit是由 Reanimated 主动注入的,而 0.14 要求该函数必须通过 Hermes 提供的标准 worklet 注册 API 声明,否则运行时直接忽略。Polyfill 行为层:Hermes 自带轻量级 Polyfill(如
Promise、Array.from),但不同版本对 Edge Case 的处理逻辑不同。0.12 中Promise.allSettled返回的status: "fulfilled"对象,在 0.14 中可能被标准化为status: "resolved"(实际未变,但某些第三方库的类型守卫会因此失效)。
提示:RN 官方文档中的“兼容矩阵”仅表示“能编译通过”,不等于“能运行正确”。矩阵只校验了 Hermes C++ 运行时与 RN Java/Kotlin 桥接层的 ABI 兼容性,完全不涉及 JS 层行为一致性。这是绝大多数升级失败的根源。
2.2 从 RN 0.71 到 0.74 的 Hermes 升级路径图谱
我们梳理了近三代 RN 主版本对应的 Hermes 推荐路径(基于 Facebook 官方 Changelog 及社区实测反馈):
| RN 版本 | 推荐 Hermes 版本 | 关键变更摘要 | 升级风险等级 |
|---|---|---|---|
| 0.71.x | 0.12.0(官方默认) | 初始稳定版,Polyfill 较宽松,worklet 注入方式灵活 | ★★☆☆☆(低) |
| 0.72.x | 0.13.0(推荐) | 引入字节码签名机制,强制要求 Metro 0.76+;global挂载点开始收敛 | ★★★☆☆(中) |
| 0.73.x | 0.13.1 或 0.14.0(可选) | 0.13.1 修复 0.13.0 的内存泄漏;0.14.0 引入 strict mode 默认开启 | ★★★★☆(高) |
| 0.74.x | 0.14.0(强制) | 移除对 0.12/0.13 的兼容支持;Require 语句解析逻辑重构 | ★★★★★(极高) |
注意:表格中“推荐版本”并非绝对唯一解。例如,RN 0.72 官方支持 Hermes 0.12~0.13,但若项目重度依赖 Reanimated 2.14+,则必须升至 0.13+,因为 Reanimated 2.14 的 worklet 编译器已适配 Hermes 0.13 的新 ABI。这意味着你的升级决策不能只看 RN 版本,而要拉通整个依赖树做影响分析。
2.3 为什么“手欠升级”大概率失败?——三个被忽视的隐性耦合点
所谓“手欠”,往往源于对以下三个隐性耦合点的无知:
Metro 打包器版本锁死:Hermes 字节码生成由 Metro 的
hermes-transformer插件完成。该插件版本必须与 Hermes 运行时版本严格匹配。例如,Hermes 0.14.0 要求@react-native/metro-config≥ 0.76.0,而该版本又强制依赖 Metro 0.76+。如果你的项目还在用 Metro 0.73,即使强行安装 Hermes 0.14,打包阶段就会因transformer找不到generateBytecode方法而报错。Native 桥接层 ABI 断裂:Hermes 升级后,C++ 运行时导出的 JNI 函数签名可能变化。RN 的
ReactInstanceManager初始化时会校验这些符号。若你未同步升级react-native本身(比如只升 Hermes 不升 RN),JNI 加载失败会导致 App 启动即闪退,错误日志里只有dlopen failed: cannot locate symbol 'hermes::vm::Runtime::create'这类晦涩提示。第三方库的 Hermes 模式开关:像
react-native-reanimated、react-native-screens等库,内部有#ifdef HERMES_ENABLED的条件编译分支。它们的预编译二进制(.aar/.framework)是按特定 Hermes 版本构建的。若你升级 Hermes 但未重新编译这些库(即未执行cd ios && pod install或./gradlew clean),旧二进制仍会尝试调用已废弃的 Hermes C++ API,引发SIGSEGV。
这三个点,任何一个没对齐,都会让升级变成一场灾难。而它们在package.json里没有任何显式声明,全靠开发者手动推演。
3. 升级前必做的五项深度检查清单
3.1 检查 1:确认当前 Hermes 实际运行版本(别信 package.json)
很多项目package.json里写的"hermes-engine": "^0.12.0",但实际运行的可能是 RN 内置的 Hermes(如 RN 0.71 自带 0.12.0)。必须通过运行时确认:
# Android:在 adb logcat 中搜索 adb logcat | grep -i "hermes version" # iOS:在 Xcode Console 中搜索 # 运行时会输出类似:[Hermes] Initialized Hermes v0.12.0-20230515更可靠的方法是在 JS 层加一行诊断代码:
// App.js 开头 if (global.HermesInternal) { console.log('Hermes version:', global.HermesInternal.version); console.log('Hermes build type:', global.HermesInternal.buildType); }注意:此代码必须放在
AppRegistry.registerComponent之前,否则可能因 Hermes 初始化顺序问题无法执行。我曾因这行代码放错位置,误判版本导致后续排查方向全错。
3.2 检查 2:绘制完整依赖兼容图谱(重点标红冲突项)
打开终端,运行:
# 生成当前项目所有依赖的 Hermes 相关声明 npx depcheck --ignores="*jest*,*typescript*" | grep -E "(hermes|reanimated|screens|navigation)"然后人工核查以下关键库的官方文档:
react-native-reanimated:查看其 Compatibility Table ,确认你的 RN 版本 + Hermes 版本组合是否被明确支持。例如,Reanimated 2.14.4 明确要求 Hermes ≥ 0.13.0。react-native-screens:其 3.25.0 版本起,Hermes 支持从“实验性”转为“正式支持”,但要求 Hermes ≥ 0.13.1。react-native-gesture-handler:2.14.0 版本修复了 Hermes 0.14 下的长按手势丢失 Bug。
将这些信息整理成一张表格,标出所有“必须同步升级”的依赖项。我的项目就因此发现:不升级 Reanimated 到 2.14.4,Hermes 0.14 的 worklet 就永远无法注册成功。
3.3 检查 3:验证 Metro 与 Transformer 版本锁链
运行以下命令,确认 Metro 及其插件版本:
# 查看 Metro 核心版本 npx metro --version # 查看 Hermes Transformer 版本(关键!) npm list @react-native/metro-config # 或 yarn list @react-native/metro-config对照 Hermes 官方文档的 Transformer Compatibility Matrix :
| Hermes 版本 | 最低 Metro 版本 | 必需 @react-native/metro-config 版本 |
|---|---|---|
| 0.12.x | 0.73.0 | 0.73.0 |
| 0.13.x | 0.76.0 | 0.76.0 |
| 0.14.x | 0.76.0 | 0.76.0 |
如果版本不匹配,必须先升级 Metro。但注意:Metro 升级可能破坏自定义 transformer(如你写了sourceExts或resolver),务必提前备份metro.config.js并在升级后逐项验证。
3.4 检查 4:审计全局变量污染与非标 API 调用
Hermes 0.13+ 开始严格限制global对象的写入。搜索项目中所有global.xxx =的赋值语句:
# Linux/macOS grep -r "global\." src/ --include="*.js" --include="*.ts" | grep -v "node_modules"重点关注以下高危模式:
global.__reanimatedWorkletInit = ...:必须改为使用ReanimatedWorklet.init()(Reanimated 2.14+ 提供的新 API)。global.__DEV__ = false:Hermes 0.14 会忽略此赋值,应改用__DEV__常量或环境变量控制。global.fetch = ...:Hermes 自带fetchPolyfill,覆盖它可能导致网络请求异常。
此外,检查所有eval()、Function()构造函数调用。Hermes 0.14 默认禁用eval,若必须使用,需在hermes-engine启动参数中显式开启(不推荐)。
3.5 检查 5:准备降级回滚方案(不是“万一”,而是“必然需要”)
任何 Hermes 升级都应预设回滚路径。我建议准备三套方案:
方案 A(最快):保留旧版
node_modules/hermes-engine文件夹副本,并记录yarn.lock中对应版本哈希。回滚时只需rm -rf node_modules/hermes-engine && cp -r hermes-0.12.0-backup node_modules/hermes-engine && yarn install。方案 B(标准):创建 Git 分支
hermes-upgrade-0.14,并在升级前提交一个pre-hermes-upgradecommit。回滚只需git reset --hard pre-hermes-upgrade。方案 C(生产兜底):在 CI/CD 流水线中增加 Hermes 版本环境变量(如
HERMES_VERSION=0.12.0),通过sed命令动态替换package.json。这样线上可一键切回旧版,无需重新部署。
实操心得:我在第一次失败后,花了 47 分钟才定位到是 Reanimated 版本不匹配。如果当时有方案 A,30 秒就能回滚并继续调试。时间就是生产力,回滚方案不是备胎,是主驾。
4. 升级过程的七步实操分解与避坑指南
4.1 步骤 1:锁定基础环境,杜绝“边升级边开发”
升级前,必须冻结所有并行开发:
- 暂停所有 Feature 分支合并;
- 将
main分支打上hermes-prep-0.14tag; - 清空本地
node_modules、ios/Pods、android/app/build; - 使用
nvm use 18.17.0(或项目指定 Node 版本)确保环境一致。
提示:Node 版本不匹配是隐形杀手。Hermes 0.14 的编译脚本依赖 Node 18+ 的
fs.promises.rmAPI,若用 Node 16,yarn install会静默跳过 Hermes 编译步骤,导致最终打包的是空字节码文件。
4.2 步骤 2:分阶段升级依赖,严禁“一锅炖”
按以下顺序执行,每步完成后必须验证 App 能冷启动:
先升 Metro 及 Transformer:
yarn add --dev @react-native/metro-config@0.76.0 metro-react-native-babel-preset@0.76.0 # 修改 metro.config.js,确保启用 Hermes const {getDefaultConfig} = require('@react-native/metro-config'); const config = getDefaultConfig(__dirname); config.transformer = { ...config.transformer, babelTransformerPath: require.resolve('metro-react-native-babel-preset'), }; module.exports = config;再升 Hermes 引擎:
yarn add hermes-engine@0.14.0 # 注意:不要加 ^ 或 ~,必须精确锁定 0.14.0最后升强耦合库(按依赖图谱顺序):
yarn add react-native-reanimated@2.14.4 yarn add react-native-screens@3.27.0 # 每升一个,立即执行下一步验证
注意:
yarn add后必须运行cd ios && pod install --repo-update(iOS)和./gradlew clean(Android),否则 Native 层不会重新链接 Hermes。
4.3 步骤 3:修改 Native 配置,绕过常见编译陷阱
iOS 配置要点:
- 打开
ios/Podfile,确认use_react_native!块中hermes_enabled设为true:use_react_native!( :path => config[:reactNativePath], :hermes_enabled => true # 必须为 true ) - 在
ios/YourApp.xcworkspace中,进入Build Settings→Search Paths→Framework Search Paths,添加:
否则 Xcode 会找不到$(SRCROOT)/../node_modules/hermes-engine/destrootlibhermes.a。
Android 配置要点:
- 打开
android/app/build.gradle,确认project.ext.react中hermesEnabled为true:project.ext.react = [ enableHermes: true, // ← 必须为 true ] - 在
android/gradle.properties中,确保hermesCommand指向正确的可执行文件:# Hermes 0.14 的编译器路径已变更 hermesCommand=./node_modules/hermes-engine/%OS-BIN%/hermes
实操心得:我在 Android 上卡了 2 小时,就因为
hermesCommand还指向旧版hermes脚本(路径是hermes-cli),而 0.14 已移除此 CLI,直接用hermes二进制。错误日志里只有Command not found,毫无线索。
4.4 步骤 4:重写 Metro 配置,激活 Hermes 字节码生成
默认的 Metro 配置不会为 Hermes 生成.hbc文件。必须在metro.config.js中显式启用:
const {getDefaultConfig} = require('@react-native/metro-config'); const config = getDefaultConfig(__dirname); // 关键:启用 Hermes Transformer config.transformer = { ...config.transformer, // 指向 Hermes 专用 transformer babelTransformerPath: require.resolve('react-native-v0.74-hermes-transformer'), // 或使用通用 transformer(推荐) babelTransformerPath: require.resolve('metro-react-native-babel-preset'), }; // 关键:设置 Hermes 输出路径 config.serializer = { ...config.serializer, getModulesRunBeforeMainModule: () => [ require.resolve('react-native/Libraries/Core/InitializeCore'), ], }; module.exports = config;然后在android/app/build.gradle中,确认hermesFlags包含-O(优化标志):
project.ext.react = [ enableHermes: true, hermesFlags: ["-O"] // 启用字节码优化 ]提示:不加
-O,生成的.hbc文件体积会大 3 倍,且启动速度下降 40%。这是 Hermes 官方文档里埋得很深的性能彩蛋。
4.5 步骤 5:JS 层代码改造,修复三大高频崩溃点
崩溃点 1:Reanimated Worklet 失效
旧写法(Hermes 0.12):
global.__reanimatedWorkletInit = () => { 'worklet'; // worklet 逻辑 };新写法(Hermes 0.14 + Reanimated 2.14.4):
import { ReanimatedWorklet } from 'react-native-reanimated'; ReanimatedWorklet.init(() => { 'worklet'; // worklet 逻辑 });崩溃点 2:console.log不输出
Hermes 0.14 默认关闭console重定向。在index.js开头添加:
if (__DEV__ && global.HermesInternal) { // 强制启用 Hermes console global.HermesInternal.enableConsole(true); }崩溃点 3:require循环引用报错
Hermes 0.14 严格校验模块循环依赖。搜索require('./xxx'),将所有循环引用改为import()动态导入:
// 旧:造成循环 // A.js -> require('./B.js') // B.js -> require('./A.js') // 新:打破循环 // A.js -> const B = await import('./B.js');4.6 步骤 6:构建与真机验证,拒绝模拟器幻觉
必须用真机测试:iOS 模拟器不启用 Hermes(它用 JSC),Android 模拟器的 Hermes 性能与真机差异巨大。我曾在模拟器上一切正常,一上 iPhone 12 就白屏。
构建命令必须带参数:
# iOS npx react-native run-ios --mode=Release --scheme=YourApp # Android npx react-native run-android --variant=release验证指标:
- 启动时间(从点击图标到首屏渲染):应 ≤ 1.2s(Hermes 0.14 目标);
- 内存占用(Xcode Debug Navigator / Android Profiler):比旧版下降 ≥ 15%;
console.log输出完整性:在 Xcode Console / Logcat 中确认所有日志可见。
4.7 步骤 7:灰度发布与监控,把风险关进笼子
上线前,必须做三层灰度:
- 技术灰度:在
index.js中加入设备 ID 白名单,仅对 5% 的内测用户启用 Hermes 0.14; - 业务灰度:通过远程配置(如 Firebase Remote Config),对特定业务线(如“我的订单”页)开启 Hermes;
- 数据灰度:埋点监控
Hermes.version和performance.memory.totalJSHeapSize,对比旧版基线。
实操心得:我们上线后发现 Hermes 0.14 在低端 Android 设备(如 Redmi Note 8)上,
totalJSHeapSize突增 200MB。原因是其 GC 策略对小内存设备不友好。我们立刻通过远程配置关闭了该机型的 Hermes,避免大规模崩溃。没有监控,就没有灰度。
5. 常见问题与排查技巧实录:来自 7 次失败的真实战场笔记
5.1 问题 1:Error: Cannot find module 'hermes-engine'—— 你以为装了,其实没装
现象:yarn add hermes-engine@0.14.0后,npx react-native run-android报错找不到模块。
根因:hermes-engine是一个“二进制分发包”,其postinstall脚本会根据系统下载对应平台的hermes可执行文件。若网络不稳定,下载会静默失败,node_modules/hermes-engine文件夹为空。
排查命令:
ls -la node_modules/hermes-engine/ # 正常应有:bin/ lib/ package.json README.md # 若只有 package.json,则下载失败解决方案:
# 清理并重试(关键:加 --network-timeout) yarn remove hermes-engine yarn add hermes-engine@0.14.0 --network-timeout 100000 # 或手动下载(以 macOS 为例) mkdir -p node_modules/hermes-engine/bin curl -L https://github.com/facebook/hermes/releases/download/v0.14.0/hermes-macos.tar.gz | tar -xz -C node_modules/hermes-engine/bin5.2 问题 2:TypeError: undefined is not an object (evaluating 'global.__reanimatedWorkletInit')—— Worklet 注册链断裂
现象:Reanimated 的动画完全不动,Debug 模式下console.log无输出。
根因:Hermes 0.14 移除了对global对象的非标写入支持,__reanimatedWorkletInit赋值被忽略。
验证方法:
// 在 App.js 中加 console.log('global.__reanimatedWorkletInit:', global.__reanimatedWorkletInit); // Hermes 0.12 输出:function ... // Hermes 0.14 输出:undefined解决方案:必须升级 Reanimated 并改用新 API,如前所述。切记:yarn add react-native-reanimated@2.14.4后,必须重新运行pod install(iOS)和./gradlew clean(Android),否则 Native 层仍加载旧版 Reanimated 二进制。
5.3 问题 3:FATAL EXCEPTION: mqt_js—— JNI 符号未找到
现象:Android App 启动即闪退,Logcat 报dlopen failed: cannot locate symbol 'hermes::vm::Runtime::create'。
根因:react-native版本与 Hermes 版本 ABI 不匹配。RN 0.71 的 C++ 桥接层调用的是 Hermes 0.12 的符号,而你装了 0.14。
快速判断:
# 查看 RN 版本 cat node_modules/react-native/package.json | grep version # 查看 Hermes 版本 cat node_modules/hermes-engine/package.json | grep version # 若 RN < 0.72 且 Hermes > 0.12,则必然冲突解决方案:要么降级 Hermes,要么升级 RN。我们选择了后者,但必须同步升级所有 RN 相关依赖(@react-native-community/cli,@react-native/assets等),否则会出现Cannot find module 'react-native/scripts/react-native-xcode.sh'等衍生错误。
5.4 问题 4:Metro Bundler卡在Building JavaScript bundle...—— 字节码编译死锁
现象:npx react-native start后,Metro 一直卡在Building JavaScript bundle...,CPU 占用 100%。
根因:Hermes 0.14 的字节码编译器对某些正则表达式(如/(?:^|\.|\[)(\w+)(?=\[|\.|$)/g)存在无限循环 Bug。
排查方法:
# 启动 Metro 时加 --verbose npx react-native start --verbose # 观察最后一条日志,若停在 `hermes-transformer: compiling ...`,则大概率是此 Bug解决方案:在metro.config.js中禁用 Hermes Transformer 的enableBytecode选项(临时):
config.transformer = { ...config.transformer, experimentalImportSupport: false, // 临时关闭 bytecode,先跑通 // hermesTransformerPath: require.resolve('...'), };待项目稳定后,再逐个排查触发死锁的 JS 文件(通常是工具函数库中的复杂正则)。
5.5 问题 5:White Screen on Launch—— 首屏白屏超 10 秒
现象:App 图标点击后,黑屏 2 秒 → 白屏 8 秒 → 才显示首页。
根因:Hermes 0.14 的字节码加载器在低端设备上初始化过慢,且未启用lazy加载。
解决方案:在android/app/build.gradle中,为 Hermes 添加lazy标志:
project.ext.react = [ enableHermes: true, hermesFlags: ["-O", "-lazy"] // ← 关键:启用懒加载 ]同时,在 JS 层优化首屏依赖:
// index.js import { AppRegistry } from 'react-native'; import App from './App'; // 延迟加载非首屏模块 setTimeout(() => { require('./src/NonCriticalModule'); // 避免阻塞首屏 }, 0); AppRegistry.registerComponent('YourApp', () => App);5.6 问题排查速查表
| 现象 | 最可能根因 | 快速验证命令 | 解决方案优先级 |
|---|---|---|---|
dlopen failed: cannot locate symbol | RN 与 Hermes ABI 不匹配 | cat node_modules/react-native/package.json | grep version | ★★★★★(必须先解决) |
global.xxx is undefined | Hermes 0.13+ 移除非标挂载 | console.log(global.xxx) | ★★★★☆(JS 层改造) |
Metro 卡在Building bundle | 正则死锁或内存不足 | npx react-native start --verbose | ★★★☆☆(临时禁用 bytecode) |
| 真机白屏,模拟器正常 | 模拟器未启用 Hermes | adb logcat | grep -i hermes | ★★★★★(必须真机验证) |
console.log不输出 | Hermes console 未启用 | global.HermesInternal?.enableConsole?.(true) | ★★☆☆☆(一行代码修复) |
实操心得:这张表是我从 7 次失败中提炼的“急救手册”。每次遇到新问题,我先对照此表,80% 的问题能在 5 分钟内定位。剩下的 20%,往往是两个以上问题叠加,这时就要祭出终极武器:
git bisect,用二分法精准定位引入问题的那次提交。
6. 经验总结:升级不是目标,稳定交付才是终点
这次“手欠升级”带来的最大收获,不是学会了 Hermes 的某个 flag,而是彻底重塑了我对 RN 生态升级的认知框架。Hermes 不是一个可以独立演进的模块,它是 RN 整个技术栈的“压力传感器”——当它报警时,暴露的永远是底层依赖的脆弱性、构建流程的随意性、以及监控体系的缺失。我后来复盘发现,真正导致失败的,不是那行yarn add hermes-engine,而是项目里早已存在的三个“技术债”:一是metro.config.js从未版本化管理,二是Podfile.lock被 gitignore 掉,三是没有建立 JS 崩溃率的基线监控。Hermes 升级,只是压垮骆驼的最后一根稻草。
所以,如果你正站在升级的门口,请先问自己三个问题:第一,你的 Metro 配置是否经得起版本迭代?第二,你的 Native 依赖是否全部 pinned 到精确版本?第三,你的线上监控能否在 1 分钟内告诉你 Hermes 是否在崩溃?如果任一答案是否定的,那么请把 Hermes 升级计划暂停,先去还债。因为真正的经验,从来不是“如何升级”,而是“如何让升级变得不那么可怕”。我现在每次升级前,都会花 2 小时做一件事:把package.json、yarn.lock、ios/Podfile.lock、android/app/build.gradle四个文件的 diff 结果打印出来,贴在工位上。看着那些密密麻麻的版本号变更,恐惧感会消退,取而代之的是一种掌控感——原来所谓资深,不过是把每一次“手欠”,都变成了下一次“手稳”的伏笔。