逃生舱口
你的某些组件可能需要控制并与 React 外部的系统同步。例如,你可能需要使用浏览器 API 聚焦一个输入框,播放或暂停一个非 React 实现的视频播放器,或者连接并监听来自远程服务器的消息。在本章中,你将学习允许你“跳出” React 并连接到外部系统的逃生舱口。你的大部分应用逻辑和数据流不应依赖这些功能。
使用 ref 引用值
当你希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,你可以使用ref:
与状态类似,ref 在重新渲染之间由 React 保留。然而,设置状态会重新渲染组件,而更改 ref 则不会!你可以通过ref.current属性访问该 ref 的当前值。
ref 就像是组件的一个秘密口袋,React 不会跟踪它。例如,你可以使用 ref 来存储超时 ID、DOM 元素以及其他不影响组件渲染输出的对象。
使用 ref 操作 DOM
React 会自动更新 DOM 以匹配你的渲染输出,因此你的组件通常不需要操作它。然而,有时你可能需要访问由 React 管理的 DOM 元素——例如,聚焦一个节点、滚动到它,或者测量其大小和位置。React 中没有内置的方法来执行这些操作,因此你需要一个指向 DOM 节点的 ref。例如,点击按钮将使用 ref 聚焦输入框:
使用 Effect 进行同步
有些组件需要与外部系统同步。例如,你可能希望基于 React 状态控制一个非 React 组件,建立服务器连接,或者在组件出现在屏幕上时发送分析日志。与处理特定事件的事件处理程序不同,Effect允许你在渲染后运行一些代码。使用它们来将你的组件与 React 外部的系统同步。
多次按下播放/暂停,观察视频播放器如何与isPlaying属性值保持同步:
许多 Effect 还会在运行后进行“清理”。例如,一个用于建立聊天服务器连接的 Effect 应该返回一个清理函数,告诉 React 如何将你的组件与该服务器断开连接:
在开发环境下,React 会立即额外运行并清理一次你的 Effect。这就是为什么你会看到"✅ Connecting..."被打印了两次。这确保了你不会忘记实现清理函数。
你可能不需要 Effect
Effect 是 React 范式的一个逃生舱。它们让你可以“跳出” React,并将你的组件与某些外部系统同步。如果不涉及外部系统(例如,当某些 props 或 state 改变时,你只想更新组件的状态),你就不应该需要 Effect。移除不必要的 Effect 将使你的代码更易于理解、运行更快且更不易出错。
有两种常见情况你不需要 Effect:
- 你不需要 Effect 来为渲染转换数据。
- 你不需要 Effect 来处理用户事件。
例如,你不需要一个 Effect 来根据其他状态调整某些状态:
相反,尽可能在渲染期间进行计算:
然而,你确实需要 Effect 来与外部系统同步。
响应式 Effect 的生命周期
Effect 的生命周期与组件不同。组件可能会挂载、更新或卸载。一个 Effect 只能做两件事:开始同步某些东西,以及稍后停止同步它。如果你的 Effect 依赖于随时间变化的 props 和 state,这个循环可能会发生多次。
这个 Effect 依赖于roomIdprop 的值。Props 是响应式值,这意味着它们可以在重新渲染时改变。请注意,如果重新同步(并重新连接到服务器):roomId发生变化,Effect 会
React 提供了一个 linter 规则来检查你是否正确指定了 Effect 的依赖项。如果你忘记在上面的示例中的依赖项列表中指定roomId,linter 会自动发现这个错误。
将事件与 Effect 分离
事件处理函数仅在您再次执行相同交互时重新运行。与事件处理函数不同,如果 Effect 读取的任何值(如 props 或 state)与上次渲染时不同,Effect 会重新同步。有时,您希望混合这两种行为:一个 Effect 会响应某些值而重新运行,但不响应其他值。
Effect 内部的所有代码都是响应式的。 如果它读取的某个响应式值由于重新渲染而发生变化,它将再次运行。例如,如果 roomId 或 theme发生变化,这个 Effect 将重新连接到聊天室:
这并不理想。您希望仅在 roomId发生变化时才重新连接到聊天室。切换theme 不应该重新连接到聊天室!将读取 theme的代码从您的 Effect 中移出,放入一个Effect Event中:
Effect Event 内部的代码不是响应式的,因此更改theme不再导致您的 Effect 重新连接。
移除 Effect 依赖项
当您编写 Effect 时,linter 会验证您是否已将 Effect 读取的每个响应式值(如 props 和 state)包含在 Effect 的依赖项列表中。这确保您的 Effect 与组件的最新 props 和 state 保持同步。不必要的依赖项可能导致您的 Effect 运行过于频繁,甚至创建无限循环。移除它们的方式取决于具体情况。
例如,这个 Effect 依赖于options对象,该对象在您每次编辑输入时都会重新创建:
你不希望每次在聊天中输入消息时都重新连接聊天。为了解决这个问题,将 options对象的创建移到 Effect 内部,这样 Effect 就只依赖于roomId字符串:
请注意,你并没有一开始就编辑依赖项列表来移除 options依赖项。那样做是错误的。相反,你修改了周围的代码,使得该依赖项变得不必要。请将依赖项列表视为 Effect 代码所使用的所有响应式值的列表。你不是有意选择将什么放入该列表。该列表描述的是你的代码。要更改依赖项列表,请更改代码。
使用自定义 Hook 复用逻辑
React 内置了诸如 useState、useContext 和 useEffect等 Hook。有时,你可能希望有一个用于更特定目的的 Hook:例如,用于获取数据、跟踪用户是否在线或连接到聊天室。为此,你可以为应用程序的需求创建自己的 Hook。
在此示例中,自定义 HookusePointerPosition跟踪光标位置,而自定义 HookuseDelayedValue 返回一个“滞后”于你传入的值一定毫秒数的值。将光标移动到沙盒预览区域上方,可以看到跟随光标移动的一串圆点轨迹:
你可以创建自定义 Hook,将它们组合在一起,在它们之间传递数据,并在组件之间复用它们。随着应用程序的增长,你将减少手动编写 Effect 的情况,因为你将能够复用已经编写的自定义 Hook。React 社区也维护了许多优秀的自定义 Hook。
下一步是什么?
前往 使用 Ref 引用值 开始逐页阅读本章!
