有时我们需要在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中找到,链接如下: