快速刷新 (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 工具文件导入了它。这种情况下,建议将该常量迁移到单独的文件中,然后由两个文件分别导入。这将恢复快速刷新的正常工作。其他情况通常也可以通过类似方式解决。
错误恢复能力
语法错误
如果您在开发过程中出现语法错误,修复后重新保存文件即可。错误会自动消失,无需重新加载应用。组件状态不会丢失。
运行时错误
如果您的组件中出现导致运行时错误的代码,将会显示上下文错误遮罩层。修复错误后,遮罩层会自动消失,无需重新加载应用。
若错误未发生在渲染过程中,组件状态将被保留。若错误发生在渲染过程中,React 将使用更新后的代码重新挂载您的应用。
如果应用中配置了 错误边界 (error boundaries)(这在生产环境中实现优雅降级是个好做法),它们会在渲染错误后的下一次编辑时重试渲染。这意味着使用错误边界可以避免应用状态总是被重置到根状态。但请注意,错误边界不应划分得 过于 细粒度。它们在 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
在可能的情况下,快速刷新会尝试保留组件编辑之间的状态。特别是,只要不更改参数或 Hook 调用顺序,useState
和 useRef
会保留其先前值。
具有依赖项的 Hook(如 useEffect
、useMemo
和 useCallback
)在快速刷新期间会 始终 更新。此时它们的依赖项列表将被忽略。
例如,当您将 useMemo(() => x * 2, [x])
编辑为 useMemo(() => x * 10, [x])
时,即使依赖项 x
未更改也会重新执行。如果 React 不这样做,您的编辑将不会反映在屏幕上!
有时这可能导致意外结果。例如,即使是依赖项数组为空的 useEffect
在快速刷新期间也会重新执行一次。
然而,编写能够适应 useEffect
偶尔重新执行的代码是良好实践,即使没有快速刷新也是如此。这将便于您后续添加新的依赖项,同时也是我们强烈建议启用的 React 严格模式 (React Strict Mode) 所要求的。