最陌生的hooks: useImperativeHandle
作者:普拉斯强
来源:SegmentFault 思否社区
估计都比较熟悉这些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对象。
- crea- teHandle:- 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 最佳实践
- 尽量避免命令式地给 - ref.current赋值,尽量采用声明式的(即让React内部处理);
- 和 - forwardRef搭配使用
 这个不一定,比如上面fomik库就没有这样做。
三、原理
ref的:- 期初利用 - ref访问子组件的实例或则DOM元素;
- 后来 - useRef出现了,我们在函数组件里利用- useRef还可以存储一些类似成员变量的数据。
- 给 - ref.current赋值是个副作用,所以一般在- Did函数或者事件处理函数里给- ref.current赋值;
- 组件在卸载时要清理 - ref.current的值。
useImperativeHandle就是在帮我们做这些事情。四、为什么需要useImperativeHandle
ref可以访问子组件实例或者DOM元素,这其实相当于子组件向父组件输出本身实例或者DOM元素。而利用useImperativeHandle子组件可以向父组件输出任意数据。
评论
