如何在一个可重用的组件内部使用ref并转发ref

116 阅读2分钟

有时我们需要在React中强制性地做一些事情--例如,将焦点设置到一个input 的元素。

useRef 钩子允许我们访问HTML元素,并强制调用其方法。也有forwardRef用于从一个可重用的组件中转发引用。

如果我们想在一个可重用的组件内部使用ref,并转发ref呢?这篇文章介绍了如何做到这一点。

一个可重用的Input 组件

我们有一个可重复使用的Input 组件,它包含一个带有工具提示的input 元素:

type Props = React.ComponentPropsWithoutRef<"input"> & {
  tooltip?: React.ReactNode;
};
export const Input = (props: Props) => {
  const internalRef = React.useRef<HTMLInputElement>(null);
  const [
    popperElement,
    setPopperElement
  ] = React.useState<HTMLDivElement | null>(null);
  const { styles, attributes } = usePopper(internalRef.current, popperElement);
  const [showTooltip, setShowTooltip] = React.useState(false);

  return (
    <>
      <input
        ref={internalRef}
        className="input"
        {...props}
        onMouseEnter={() => setShowTooltip(true)}
        onMouseLeave={() => setShowTooltip(false)}
      />
      <div
        ref={setPopperElement}
        className="tooltip"
        style={{ ...styles.popper, display: showTooltip ? "block" : "none" }}
        {...attributes}
      >
        {props.tooltip}
      </div>
    </>
  );
};

react-popperreact-popper 需要一个对input 元素的引用 - 我们使用一个叫做internalRef 的变量,它使用useRef 来实现。

因此,我们的可重复使用的Input 组件在内部使用input 元素的引用。

添加forwardRef

目前,Input 组件的消费者还没有一个指向input 元素的引用,以做一些事情,如设置焦点到它。

我们需要将forwardRef 添加到Input ,以便给消费者一个指向input 元素的参考:

export const Input = React.forwardRef<  HTMLInputElement,  Props>((props, ref) => {  const internalRef = React.useRef<HTMLInputElement>(null);
  ...
  return (
    <>
      <input
        ref={internalRef}
        ...
      />
      ...
    </>
  );
});

这样做的问题是转发的引用,ref ,并没有与input 元素的引用连接起来,internalRef

useImperativeHandle

有一个 useImperativeHandle钩子,我们可以用它来连接这些引用:

const internalRef = React.useRef<HTMLInputElement>(null);

// 💥 Type 'HTMLInputElement | null' is not assignable to type 'HTMLInputElement'
React.useImperativeHandle(ref, () => internalRef.current);

useImperativeHandle 在这个过程中,我们需要转发的引用和一个函数来返回已解决的引用。

但上面给出了一个类型错误。这是因为ref不应该有一个null 的值。😞

为了解决类型错误,我们可以使用useImperativeHandle 的通用参数:

React.useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
    ref,
    () => internalRef.current
  );

Input 组件的消费者现在可以使用它的 ref 来设置焦点到input 元素:

const inputRef = React.useRef<HTMLInputElement>(null);

React.useEffect(() => {
  if (inputRef.current) {
    inputRef.current.focus();
  }
}, []);
return (
  <Input
    ref={inputRef}
    type="text"
    tooltip="Enter something interesting"
  />
);

很好!😊

这篇文章的代码可以在Codesandbox中找到,链接如下:

🏃 播放代码