快速刷新 (Fast Refresh)
快速刷新 (Fast Refresh) 是 Next.js 集成的 React 功能,当您保存文件变更时,可以在保持客户端临时状态的同时实时重载浏览器页面。该功能在 9.4 及以上版本 的所有 Next.js 应用中默认启用。启用后,大多数编辑操作的效果都能在一秒内可见。
工作原理
- 如果您编辑的文件 仅导出 React 组件,快速刷新会仅更新该文件的代码并重新渲染对应组件。您可以修改该文件内的任何内容,包括样式、渲染逻辑、事件处理函数或副作用 (effects)。
- 如果您编辑的文件包含 非 React 组件的导出,快速刷新会重新执行该文件及其所有导入文件。例如
Button.js
和Modal.js
都导入了theme.js
,那么编辑theme.js
会同时更新这两个组件。 - 最后,如果您编辑的文件被 React 组件树之外的文件导入,快速刷新 将回退为完全重载。例如某个文件既渲染 React 组件,又导出一个被 非 React 组件 导入的值。这种情况下,建议将该常量迁移到独立文件,然后由原文件和工具文件共同导入。其他类似情况通常也可用相同方式解决。
错误恢复能力
语法错误
开发过程中出现语法错误时,修复后再次保存文件即可。错误会自动消失,无需重载应用。组件状态不会丢失。
运行时错误
如果组件内发生运行时错误,会出现上下文错误遮罩。修复错误后遮罩会自动消失,无需重载应用。
若错误未发生在渲染阶段,组件状态会被保留;若发生在渲染阶段,React 会使用更新后的代码重新挂载应用。
如果应用中配置了 错误边界 (Error Boundary)(这在生产环境中能实现优雅降级),渲染错误后的下一次编辑会触发重试渲染。这意味着错误边界可以避免应用状态总是重置到根状态。但请注意错误边界不应_过度_细分,它们在 React 生产模式中有特定用途,应当谨慎设计。
限制
快速刷新会尽量保留编辑组件的本地 React 状态,但仅在安全时生效。以下情况会导致文件编辑时本地状态被重置:
- 类组件 (class components) 的状态不会被保留(仅函数组件和 Hooks 能保留状态)
- 编辑的文件可能包含 React 组件之外的_其他_导出
- 某些文件可能导出高阶组件 (HOC) 的调用结果如
HOC(WrappedComponent)
,若返回的是类组件,其状态会被重置 - 匿名箭头函数如
export default () => <div />;
会导致快速刷新无法保留组件状态。大型代码库可使用name-default-component
代码迁移工具
随着代码库向函数组件和 Hooks 迁移,状态保留的覆盖率将提升。
使用技巧
- 快速刷新默认会保留函数组件(及 Hooks)的 React 本地状态
- 有时您可能需要_强制_重置状态并重新挂载组件(例如调试仅会在挂载时触发的动画)。此时可在编辑文件中任意位置添加
// @refresh reset
指令。该指令仅作用于当前文件,会令快速刷新在每次编辑时重新挂载该文件定义的组件 - 开发期间可在编辑的组件中添加
console.log
或debugger;
- 注意导入语句区分大小写。当导入路径与实际文件名不匹配时(如
'./header'
与'./Header'
),快速刷新和完全重载都可能失败
快速刷新与 Hooks
在可能的情况下,快速刷新会尝试保留组件状态。特别是 useState
和 useRef
会保留之前的值,只要不修改它们的参数或 Hooks 调用顺序。
具有依赖项的 Hooks(如 useEffect
、useMemo
和 useCallback
)在快速刷新期间会_始终_更新,此时它们的依赖项数组会被忽略。
例如将 useMemo(() => x * 2, [x])
编辑为 useMemo(() => x * 10, [x])
时,即使依赖项 x
未变化也会重新执行。若不如此,编辑结果就无法反映到界面上!
有时这会导致意外结果,例如即使 useEffect
的依赖项数组为空,在快速刷新期间仍会执行一次。
但编写能适应 useEffect
偶尔重新运行的代码是良好实践,这与是否使用快速刷新无关。这能方便后续添加新依赖项,也是我们强烈推荐的 React 严格模式 (React Strict Mode) 所要求的。