头图

最陌生的hooks: useImperativeHandle

估计都比较熟悉这些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])
  1. ref
    需要被赋值的ref对象。
  2. createHandle
    createHandle函数的返回值作为ref.current的值。
  3. [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"/>;
})

image.png
看看控制台输出发现createHandle函数的执行时机和useLayoutEffect一致,这样就保证了在任意位置的useEffect里都能拿到最新的ref.current的值

注意:执行createHandle函数的还有个前提条件,即useImperativeHandle的第一个实参ref必须有值(否则执行createHandle函数也没意义啊)。

2.3 应用场景

目前项目已经有多处使用场景了,主要是解决父组件获取子组件的数据或者调用子组件的里声明的函数。
formik库的一处使用:

React.useImperativeHandle(innerRef, () => formikbag);

2.4 最佳实践

React官网里给出了几点使用建议:

  1. 尽量避免命令式地给ref.current赋值,尽量采用声明式的(即让React内部处理);
  2. forwardRef搭配使用
    这个不一定,比如上面fomik库就没有这样做。

三、原理

先回顾下我们之前是如何使用ref的:

  1. 期初利用ref访问子组件的实例或则DOM元素;
  2. 后来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.

通过之前的知识我们可以达成几点共识:

  1. ref.current赋值是个副作用,所以一般在Did函数或者事件处理函数里给ref.current赋值;
  2. 组件在卸载时要清理ref.current的值。

本质上useImperativeHandle就是在帮我们做这些事情。

四、为什么需要useImperativeHandle

我们都知道父组件可以利用ref可以访问子组件实例或者DOM元素,这其实相当于子组件向父组件输出本身实例或者DOM元素。而利用useImperativeHandle子组件可以向父组件输出任意数据。

整理自GitHub笔记:useImperativeHandle


三人行
专注前端交流分享

Coder

2.6k 声望
51 粉丝
0 条评论
推荐阅读
解析position: sticky;
粘性定位position sticky元素采用正常的文档流布局(static),当其边框(border矩形)相对于最近的滚动祖先元素的内边框(即content矩形)的小于指定阈值时,则position sticky元素相对于该最近的滚动祖先元素固...

普拉斯强3阅读 2.1k评论 1

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木143阅读 12k评论 10

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木60阅读 6k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.2k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木39阅读 7.1k评论 6

CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan42阅读 2.8k评论 14

封面图
还在用 JS 做节流吗?CSS 也可以防止按钮重复点击
举个例子:一个保存按钮,为了避免重复提交或者服务器考虑,往往需要对点击行为做一定的限制,比如只允许每300ms提交一次,这时候我想大部分同学都会到网上直接拷贝一段throttle函数,或者直接引用lodash工具库

XboxYan34阅读 2.2k评论 2

封面图

Coder

2.6k 声望
51 粉丝
宣传栏