估计都比较熟悉这些HOOKS了吧:useState
, useEffect
, useContext
, useMemo
。但我当第一次看到useImperativeHandle
时,一脸懵逼(这是什么鬼东西~~~)。
一、是什么?
React官网对useImperativeHandle
介绍也比较简短。总结一句话就是:子组件利用useImperativeHandle
可以让父组件输出任意数据。
// FancyInput组件作为子组件
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
// 命令式的给`ref.current`赋值个对象
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
}
}));
return <input ref={inputRef} ... />
})
// Example组件作为父组件
function Example() {
const fancyInputRef = useRef()
const focus = () => {
fancyInputRef.current.focus()
}
return (
<>
<FancyInput ref={fancyInputRef} />
</>
)
}
二、怎么用?
2.1 语法
useImperativeHandle(ref, createHandle, [deps])
ref
需要被赋值的ref
对象。createHandle
:createHandle
函数的返回值作为ref.current
的值。[deps]
依赖数组,依赖发生变化会重新执行createHandle
函数。
2.2 进阶:什么时候执行createHandle
函数?
测试发现和useLayoutEffect
执行时机一致。
修改下组件FancyInput
内容:
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
console.log('render 1')
useLayoutEffect(() => {
console.log('useEffect1', ref)
})
useImperativeHandle(ref, function() {
debugger
console.log('useImperativeHandle')
return {
focus: () => {
inputRef.current.focus();
}
}
})
useLayoutEffect(() => {
console.log('useEffect2', ref);
})
console.log('render 2')
return <input ref={inputRef} placeholder="FancyInput"/>;
})
看看控制台输出发现createHandle
函数的执行时机和useLayoutEffect
一致,这样就保证了在任意位置的useEffect
里都能拿到最新的ref.current
的值。
注意:执行createHandle
函数的还有个前提条件,即useImperativeHandle
的第一个实参ref
必须有值(否则执行createHandle
函数也没意义啊)。
2.3 应用场景
目前项目已经有多处使用场景了,主要是解决父组件获取子组件的数据或者调用子组件的里声明的函数。
如formik库的一处使用:
React.useImperativeHandle(innerRef, () => formikbag);
2.4 最佳实践
React官网里给出了几点使用建议:
- 尽量避免命令式地给
ref.current
赋值,尽量采用声明式的(即让React内部处理); - 和
forwardRef
搭配使用
这个不一定,比如上面fomik库就没有这样做。
三、原理
先回顾下我们之前是如何使用ref
的:
- 期初利用
ref
访问子组件的实例或则DOM元素; - 后来
useRef
出现了,我们在函数组件里利用useRef
还可以存储一些类似成员变量的数据。
再回顾下React如何处理声明式ref的:
React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts.
通过之前的知识我们可以达成几点共识:
- 给
ref.current
赋值是个副作用,所以一般在Did
函数或者事件处理函数里给ref.current
赋值; - 组件在卸载时要清理
ref.current
的值。
本质上useImperativeHandle
就是在帮我们做这些事情。
四、为什么需要useImperativeHandle
我们都知道父组件可以利用ref
可以访问子组件实例或者DOM元素,这其实相当于子组件向父组件输出本身实例或者DOM元素。而利用useImperativeHandle
子组件可以向父组件输出任意数据。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。