通过例子说明 ref 的不足
当我们需要调用子组件中的方法时,我们都是考虑使用useRef
来调用子组件的方法,具体的示例代码如下:
// 父级组件
const UseImperativeHandleDemo: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<CustInput ref={inputRef} initVal={"123"} />
<button onClick={() => inputRef.current?.focus()}>获取焦点</button>
</div>
);
};
// CustInput子组件
const CustInput = React.forwardRef<HTMLInputElement, CustInputProp>(
(
props: CustInputProp,
ref: React.ForwardedRef<HTMLInputElement>
): JSX.Element => {
return <input ref={ref} value={props.initVal} onChange={() => {}} />;
}
);
这个例子中我们只是将一个引用直接传递给组件内部的单个元素,那是没有问题的,假如我们需要功能做的更加复杂一点或者需要自定义引用时,代码开发难度可能就会大大提升。
在上述例子中使用 useImperativeHandle 钩子函数扩展自定义函数
我们在上述例子的基础上使用useImperativeHandle
钩子函数进行自定义函数的扩展,扩展后我们就可以在父级组件调用子组件的方法。
使用useImperativeHandle
钩子函数后我们就可以在子组件中随意定义方法对外暴露方法。具体的实例如下:
const CustInput = React.forwardRef<any, CustInputProp>(
(props: CustInputProp, ref: React.ForwardedRef<any>): JSX.Element => {
useImperativeHandle(
ref,
() => {
return { alertHi: () => alert(props.initVal) };
},
[]
);
return <input value={props.initVal} onChange={() => {}} />;
}
);
// 父组件
<button
onClick={() => {
inputRef.current?.alertHi(); // 调用子组件自定义的方法
}}
>
获取焦点
</button>;
使用 useImperativeHandle 钩子函数控制子组件中的多个元素
在上一小节我们简单实用了useImperativeHandle
钩子函数进行了子组件自定义函数的扩展,在本小节中,我们可以使用useImperativeHandle
钩子函数实现控制子组件中多个元素。
首先我们先看一下我们要实现的例子,具体界面如下:
在上述的页面中,我们的业务需求是让上述对应的按钮控制对应子组件中对应元素的焦点,我们可以在父级组件把对应的方法编写好以后传递给子组件,但是这样就会破坏单一职责原则,所以我们可以使用useImperativeHandle
钩子来实现,具体的代码如下:
// 父级组件
const [open, setOpen] = useState<boolean>(false)
const modalRef = useRef<any>(null)
<div>
<button onClick={() => setOpen(true)}>open</button>
<button onClick={() => modalRef.current.closeFocus()}>focus close</button>
<button onClick={() => modalRef.current.confirmFocus()}>focus confirm</button>
<button onClick={() => modalRef.current.denyFocus()}>focus deny</button>
<ConfirmationModal
ref={modalRef}
isOpen={open}
onClocse={() => setOpen(false)}
/>
</div>
// 子组件
const ConfirmationModal = React.forwardRef<any, ConfirmationModalProps>(
({ isOpen, onClocse }: ConfirmationModalProps, ref: React.ForwardedRef<any>): JSX.Element => {
const closeRef = useRef<HTMLButtonElement>(null)
const confirmRef = useRef<HTMLButtonElement>(null)
const denyRef = useRef<HTMLButtonElement>(null)
useImperativeHandle(ref, () => {
return {
closeFocus: () => closeRef.current?.focus(),
confirmFocus: () => confirmRef.current?.focus(),
denyFocus: () => denyRef.current?.focus()
}
}, [])
if (!isOpen) return <></>
return (
<div className="modal">
<button className="close-btn" ref={closeRef} onClick={(e) => onClocse()}>×</button>
<div className="modal-header">
<h1>title</h1>
</div>
<div className="modal-body">
do yo confirm?
</div>
<div className="modal-footer">
<button className="confirm-btn" ref={confirmRef} onClick={() => onClocse()}>Yes</button>
<button className="deny-btn" ref={denyRef} onClick={() => onClocse()}>No</button>
</div>
</div>
)
}
)
总结
通过上述两个简单的例子,我们可以看到useImperativeHandle
的钩子函数主要是简化我们对子组件的控制,即可以在子组件中实现自定义函数和控制子组件中的元素。这就是useImperativeHandle
钩子函数的作用。