T3 Code 非官方中文翻译不是官方产品,也不是 T3 Tools 官方文档。源提交 d2c25950b4d9英文原文GitHub 源文件
Skip to content

移动端开发生命周期 ​

连接运行时的 HMR 边界保持一个稳定的 atom 运行时,并通过一个可写 atom 替换它的 Effect 层。它只在装上新层之后才接受这次更新。否则,即使 Metro 报告刷新成功,导入方仍会保留旧行为。

不要靠重置共享的 atom 注册表来刷新对连接运行时的一处修改。重置会从已挂载的消费者身上移除监听器,而 Metro 并没有理由重新渲染这些消费者。当注册表或托管运行时模块本身发生变化时,Metro 的正常传播会释放它的旧资源。这条边界并不会让任意的模块级 atom 家族都可以安全地热替换。生产环境使用的是普通的 atom 运行时。

环境监督器作用域是注册表作用域的子级。按环境划分的那张映射支持针对性关闭,但如果某个监督器是在清理已经跑完之后才创建的,它就会逃出该作用域。已经关闭的父作用域也会关闭后到的对象,以免被中断的启动或运行时替换,在新注册表之外留下一条仍然存活的 WebSocket。

Uniwind 会在 Metro 更新时编译 CSS,以便发现新用到的类。只有在生成的样式表和主题列表都没有变化时,它才跳过全局样式失效。跳过编译会丢掉新的类;输出没有变化却让每一个消费者都失效,会使一次普通的组件修改刷新整个应用。指纹只在初始化成功之后才会被记录。

apps/mobile/modules/ 下的原生模块是 file: 依赖,pnpm 会把它们复制进自己的虚拟存储,而不是做成链接。Metro 打包的是这份副本,因此在 vp i 重新同步之前,正在运行的开发客户端看不到对模块 TypeScript 的修改;与此同时,Gradle 和 CocoaPods 直接编译工作树(worktree)目录。设备上一次“没有效果”的 JavaScript 修改,通常就是这种情况。

测量渲染 ​

render-counter.ts 统计 React 在每一次提交中渲染了哪些组件,以及原因:某个状态槽变了、props 变了,或者上下文变了。它从 index.ts 里的第一次导入开始包住 DevTools 钩子,并且只在 __DEV__ 与 EXPO_PUBLIC_T3_RENDER_COUNTER=1 同时成立时才加载,因此发布包里绝不会包含它。遍历会跳过 React 已经放弃渲染的子树。如果探针对每一条 fiber 都检查 flags & 1,就会把从未发生的渲染也报出来,因为被放弃渲染的 fiber 仍保留着更早提交留下的标志。

measure-renders.mts 从应用外部驱动一次测量。它通过 Metro 的 Hermes 检查器代理,对计数器的全局变量求值;这对 iOS 模拟器和 Android 模拟器同样适用,也不需要点按、simctl 或 adb。它经由服务器发起一次真实的提供方(provider)回合(turn),用应用链接打开对话线程,在流式传输进行到中途时录下一段窗口,并打印渲染次数的中位数、提交次数、靠前的组件和靠前的原因。Expo 会丢掉那些 Origin 并不是 Metro 所公布 URL 的检查器套接字,因此请把该 URL(带有 --lan 的局域网地址)作为 --metro 传入。

bash
EXPO_PUBLIC_T3_RENDER_COUNTER=1 vp run dev:client   # in apps/mobile, then pair the app
node apps/server/src/bin.ts auth session issue --base-dir <base-dir> --token-only
node apps/mobile/scripts/measure-renders.mts --server <server-origin> --token <token> \
  --metro http://<metro-lan-host>:8081 --project-root <scratch-dir>

--route /settings/usage 则会重新加载应用,从主页打开该路径,并在挂载和数据落定期间记录一段 6 秒的窗口;这次没有提供方回合,也不使用 --server、--token 或 --project-root。如果同时还带上 --server,应用会重新加载并打开该路径(/ 仍留在主页),回复流入一条应用从不显示的新对话线程,用来测量别处的流式传输对眼前这块屏幕的开销。

请在同一个 base、同一台设备上比较修改前后,并取三次运行的中位数。提供方的输出每次运行都不相同,因此单次运行,或者跨不同 base 的比较,都只是噪声。

React Compiler ​

Metro 会运行这个编译器,仅仅是因为 experiments.reactCompiler 被设置在 app.config.ts 里。Expo 接着把 supportsReactCompiler 传给 babel-preset-expo,插件选项由后者掌管;并没有 babel.config.js 条目可供查找。发布包使用 panicThreshold: "NONE",因此编译器无法证明其安全的函数会未经记忆化就发布出去,构建也不会报错。reactCompilerCoverage.test.ts 会对每一个源文件运行同一套预设。出现新的退出编译(bailout)时它会失败;KNOWN_BAILOUTS 里某个现在已经能够编译的文件也会让它失败,因此这份列表只会变短。修好列表中的某个文件时,要在同一次改动里删掉它的条目,以及手工添加的 useMemo/useCallback/memo。移动端的大多数退出编译,是 try 没有 catch,或带有 finally,以及在渲染期间读取 ref;常见改写见 Web 界面。移动端运行 t3code/no-manual-memo 时带有 allowMemoHooks,因此 memo 指令是错误,但在 KNOWN_BAILOUTS 变空之前,这些钩子仍然合法。t3code/no-hookless-use-function 会拒绝一个不调用任何钩子的 use* 函数,因为编译器不会缓存它。真实钩子的平台桩,例如某个 .android.ts 文件,会保留钩子的名字,并用一条理由抑制该规则。

非官方译文 · 源提交 d2c25950b4d9 · 不代表 T3 Tools