如何使用 ref 操作 DOM?(四)forwardRef 访问自己组件的 DOM 节点

1,776 阅读3分钟

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

翻译自:beta.reactjs.org/learn/manip…

因为 React 已经根据 render 的输出处理了 DOM 结构,所以你的组件不经常需要操作 DOM。然而,有的时候你可能需要操作 React 管理的 DOM 元素,比如,将焦点放到一个节点上,滚动到这个节点,或者去计算它的宽和高。React 中没有内置的方法去做这些事情,所以你将会需要 ref 去指向这个 DOM 节点。

这个系列的文章你将会学到:

  • 如何使用 ref 属性访问由 React 管理的 DOM 节点
  • 如何将 JSX 的 ref 属性关联到 useRef 钩子
  • 如何访问其他组件的 DOM 节点
  • 在哪种情况下,修改 React 管理的 DOM 是安全的

关于 ref 相关的介绍和例子,可以看我前面一个系列的文章 useRef 简单易懂解析

系列文章

访问另一个组件的 DOM 节点

当你在内置组件(会被输出为浏览器的元素比如 <input />)上设置 ref 的时候,React 会将这个 ref 的 current 属性设置为相应的 DOM 节点(比如浏览器中实际的 <input />)。 但是,如果你尝试将 ref 放在你自己的组件上,比如 <MyInput />,默认情况下,您将获得 null。这是一个演示它的示例,请注意单击按钮后不会聚焦到 input。

import { useRef } from 'react';

function MyInput(props) {
  return <input {...props} />;
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

单击该按钮将打印一个错误消息到控制台:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

警告:不能为函数组件提供引用。尝试访问此引用将失败。你的意思是使用 React.forwardRef() 吗?

发生这种情况是因为默认情况下 React 不允许组件访问其他组件的 DOM 节点。甚至自己的 children 也不行!这是故意的这样设计的。Refs 是一个逃生舱,应该谨慎使用。手动操作另一个组件的 DOM 节点会使您的代码更加脆弱。

相反,想要公开其 DOM 节点的组件必须选择加入这个行为。一个组件可以指定它将它的引用“转发”给它的一个子组件。以下是 MyInput 如何使用 forwardRef API:

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

这是它的工作原理:

  • <MyInput ref={inputRef} /> 告诉 React 将对应的 DOM 节点放入 inputRef.current 中。但是,由 MyInput 组件来选择是否加入,默认情况下,它不会加入。
  • MyInput 组件是使用 forwardRef 声明的,这让它从上一层级接收 inputRef 这个参数,作为在 props 之后声明的第二个 ref 参数。
  • MyInput 本身将它接收到的ref传递给它内部的 <input>

这样,点击按钮来聚焦 input 就可以实现了:

import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

在设计系统中,按钮、input 等低级组件将其引用转发到它们的 DOM 节点是一种常见的模式。另一方面,像表单、列表或页面这样的高级组件通常不会公开它们的 DOM 节点,以避免依赖于 DOM 结构。