第40章 Fast Refresh · React Native 开发体验
0.77版本:快速刷新
快速刷新(Fast Refresh)是 React Native 的一项功能,能让你在修改 React 组件时获得近乎即时的反馈。该功能默认开启,你可以在 React Native 开发菜单中切换“Enable Fast Refresh”。开启后,大多数代码编辑会在 1-2 秒内反映到界面上。
工作原理
如果你修改的模块只导出 React 组件,快速刷新只会更新该模块的代码并重新渲染组件。你可以修改文件中的任何内容,包括样式、渲染逻辑、事件处理函数或副作用。 如果你修改的模块导出了非 React 组件的内容,快速刷新会重新运行该模块以及所有导入它的其他模块。例如,如果 `Button.js` 和 `Modal.js` 都导入了 `Theme.js`,编辑 `Theme.js` 就会同时更新这两个组件。 最后,如果你修改了被 React 树外部模块导入的文件,快速刷新会回退到执行全量重载。你可能会遇到这样的情况:某个文件既渲染 React 组件,又导出被非 React 组件导入的值。例如,组件同时导出了一个常量,而某个非 React 工具模块导入了它。在这种情况下,建议将该常量迁移到一个单独的文件中,并在原文件和工具模块中分别导入。这样就能恢复快速刷新的功能。其他类似情况通常也能通过这种方式解决。
错误容忍
如果在快速刷新会话中出现了语法错误,你可以修复后再次保存文件,错误提示框(redbox)就会消失。由于存在语法错误的模块会被阻止运行,你无需重新加载应用。 如果在模块初始化期间发生了运行时错误(例如将 `StyleSheet.create` 误写为 `Style.create`),修复错误后快速刷新会话将继续进行。错误提示框会消失,模块也会被更新。 如果你犯了错误导致组件内部发生运行时错误,修复后快速刷新会话同样会继续。在这种情况下,React 会使用更新后的代码重新挂载(remount)你的应用程序。 如果你的应用中使用了错误边界(Error Boundaries,这在生产环境中处理优雅降级时是个好习惯),它们会在下次编辑后重试渲染。从这个意义上说,使用错误边界可以防止你总是被强制跳回到应用的根屏幕。但请记住,错误边界的粒度不宜过细。它们在 React 生产环境中也有使用,应当始终经过精心设计。
局限性
快速刷新试图在正在编辑的组件中保留局部 React 状态,但仅在安全的情况下这样做。以下原因可能导致每次编辑文件时局部状态被重置:
- 类组件(Class Components)不保留局部状态(只有函数组件和 Hooks 才保留状态)。 - 你正在编辑的模块除了 React 组件外,可能还有其他导出项。 - 有时,模块会导出高阶组件(如 `createNavigationContainer(MyScreen)`)的调用结果。如果返回的组件是类组件,状态会被重置。
从长远来看,随着代码库中更多部分迁移到函数组件和 Hooks,你可以期待在更多情况下状态得以保留。
使用技巧
- 快速刷新默认会在函数组件(和 Hooks)中保留 React 局部状态。 - 有时你可能想强制重置状态并重新挂载组件。例如,如果你正在调试一个仅在挂载时触发的动画,这就很有用。要实现这一点,你可以在正在编辑的文件中任意位置添加 `// @refresh reset`。该指令是文件局部的,它指示快速刷新在每次编辑时重新挂载该文件中定义的组件。
快速刷新与 Hooks
在可能的情况下,快速刷新会尝试在编辑之间保留组件的状态。具体来说,只要你不改变 `useState` 和 `useRef` 的参数或 Hook 调用顺序,它们就会保留之前的值。 带有依赖项的 Hook——例如 `useEffect`、`useMemo` 和 `useCallback`——在快速刷新时总会更新。快速刷新期间,它们的依赖列表会被忽略。 例如,当你将 `useMemo(() => x * 2, [x])` 编辑为 `useMemo(() => x * 10, [x])` 时,即使依赖项 `x` 没有变化,它也会重新运行。如果 React 不这样做,你的修改就不会反映在屏幕上! 有时,这可能会导致意想不到的结果。例如,即使 `useEffect` 的依赖数组为空,它在快速刷新期间也会重新运行一次。然而,编写能容忍 `useEffect` 偶尔重新运行的代码本身就是一种良好的实践,即便没有快速刷新也是如此。这能让你更容易地在后续引入新的依赖项。