使用 Ref 操作 DOM
React 会自动更新DOM以匹配你的渲染输出,因此你的组件通常不需要操作它。然而,有时你可能需要访问由 React 管理的 DOM 元素——例如,聚焦一个节点、滚动到它或测量其大小和位置。React 中没有内置的方法来做这些事情,因此你需要一个指向 DOM 节点的ref。
您将学习
- 如何使用
ref属性访问由 React 管理的 DOM 节点 - JSX 的
ref属性与useRefHook 的关系 - 如何访问另一个组件的 DOM 节点
- 在哪些情况下可以安全地修改由 React 管理的 DOM
获取节点的 ref
要访问由 React 管理的 DOM 节点,首先导入useRefHook:
然后,在你的组件内部用它声明一个 ref:
将你的 ref 作为ref属性传递给想要获取其 DOM 节点的 JSX 标签:
useRef Hook 返回一个具有名为 current的单一属性的对象。初始时,myRef.current 将为 null。当 React 为此 <div>创建 DOM 节点时,React 会将该节点的引用放入myRef.current。然后,你可以从你的 事件处理函数中访问此 DOM 节点,并使用其上定义的浏览器 API。
示例:聚焦文本输入框
在此示例中,点击按钮将使输入框获得焦点:
要实现此功能:
- 使用
useRefHook 声明inputRef。 - 将其作为
<input ref={inputRef}>传递。这告诉 React将此<input>的 DOM 节点放入inputRef.current中。 - 在
handleClick函数中,从inputRef.current读取输入 DOM 节点,并使用focus()方法调用inputRef.current.focus()。 - 通过
onClick将handleClick事件处理函数传递给<button>。
虽然 DOM 操作是 ref 最常见的用例,但useRefHook 也可用于存储 React 之外的其他内容,例如计时器 ID。与状态类似,ref 在渲染之间保持不变。ref 类似于状态变量,但设置它们时不会触发重新渲染。请在使用 Ref 引用值中阅读有关 ref 的更多信息。
示例:滚动到元素
一个组件中可以拥有多个 ref。在此示例中,有一个包含三张图片的轮播。每个按钮通过调用浏览器scrollIntoView()方法,将对应的 DOM 节点滚动到视图中心:
访问另一个组件的 DOM 节点
陷阱
Ref 是一种应急方案。手动操作另一个组件的 DOM 节点会使你的代码变得脆弱。
你可以像传递任何其他 prop 一样,将 ref 从父组件传递给子组件就像传递任何其他 prop 一样。
在上面的示例中,ref 在父组件 MyForm 中创建,并传递给子组件 MyInput。MyInput然后将 ref 传递给<input>。因为<input>是一个内置组件,React 会将 ref 的.current 属性设置为 <input>DOM 元素。
在 MyForm中创建的inputRef 现在指向 MyInput 返回的 <input> DOM 元素。在 MyForm中创建的点击处理程序可以访问inputRef并调用focus() 来聚焦 <input>。
React 何时附加 ref
在 React 中,每次更新都分为两个阶段:
- 在渲染期间,React 调用你的组件以确定屏幕上应该显示什么。
- 在提交期间,React 将更改应用到 DOM。
通常,你不希望在渲染期间访问 ref。这对于持有 DOM 节点的 ref 也是如此。在首次渲染期间,DOM 节点尚未创建,因此ref.current 将为 null。而在更新渲染期间,DOM 节点尚未更新。因此读取它们还为时过早。
React 在提交期间设置ref.current。在更新 DOM 之前,React 将受影响的ref.current值设置为null。更新 DOM 后,React 立即将它们设置为相应的 DOM 节点。
通常,你会从事件处理程序中访问 ref。如果你想对 ref 执行某些操作,但没有特定的事件来执行,你可能需要一个 Effect。我们将在接下来的页面中讨论 Effect。
使用 ref 操作 DOM 的最佳实践
Ref 是一个应急方案。你应该只在必须“跳出 React”时使用它们。常见的例子包括管理焦点、滚动位置,或者调用 React 未暴露的浏览器 API。
如果你坚持使用非破坏性操作,如聚焦和滚动,应该不会遇到任何问题。然而,如果你尝试手动修改DOM,则可能面临与 React 正在进行的更改发生冲突的风险。
为了说明这个问题,这个示例包含一条欢迎消息和两个按钮。第一个按钮使用条件渲染和状态来切换其显示,就像你在 React 中通常做的那样。第二个按钮使用remove() DOM API,在 React 的控制之外强制将其从 DOM 中移除。
尝试多次按下“使用 setState 切换”。消息应该会消失并再次出现。然后按下“从 DOM 中移除”。这将强制移除它。最后,按下“使用 setState 切换”:
在你手动移除 DOM 元素后,尝试使用setState再次显示它会导致崩溃。这是因为你已经更改了 DOM,而 React 不知道如何继续正确地管理它。
避免更改由 React 管理的 DOM 节点。修改、向由 React 管理的元素添加子元素或从其移除子元素,可能导致视觉结果不一致或如上所述的崩溃。
然而,这并不意味着你完全不能这样做。它需要谨慎。你可以安全地修改 React没有理由更新的那部分 DOM。例如,如果 JSX 中的某个<div>始终为空,React 就没有理由去接触它的子元素列表。因此,在那里手动添加或移除元素是安全的。
回顾
- Ref 是一个通用概念,但大多数情况下你会用它来持有 DOM 元素。
- 你可以通过传递
myRef.current来指示 React 将一个 DOM 节点放入<div ref={myRef}>。 - 通常,你会将 ref 用于非破坏性操作,如聚焦、滚动或测量 DOM 元素。
- 默认情况下,组件不会暴露其 DOM 节点。你可以通过使用
ref属性来选择暴露一个 DOM 节点。 - 避免更改由 React 管理的 DOM 节点。
- 如果你确实要修改由 React 管理的 DOM 节点,请修改 React 没有理由更新的部分。
Try out some challenges
Challenge 1 of 4:Play and pause the video #
In this example, the button toggles a state variable to switch between a playing and a paused state. However, in order to actually play or pause the video, toggling state is not enough. You also need to call play() and pause() on the DOM element for the <video>. Add a ref to it, and make the button work.
For an extra challenge, keep the “Play” button in sync with whether the video is playing even if the user right-clicks the video and plays it using the built-in browser media controls. You might want to listen to onPlay and onPause on the video to do that.
