useRef和useImperativeHandle

useRef 是 React 提供的一个 Hook,用于在组件的整个生命周期内持久保存一个可变的 ref 对象。它有两个主要的用途:

  1. 访问 DOM 节点:使用 useRef 来获取对 DOM 节点的直接引用。这可以用于读取 DOM 信息(如尺寸、位置等)或者调用 DOM API(比如聚焦一个输入框)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* @Description:
* @Author: xiuji
* @Date: 2024-01-26 10:34:09
* @LastEditTime: 2024-01-26 10:34:49
* @LastEditors: Do not edit
*/
import React, { useRef } from 'react';

const RefDemo = () => {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
)
}

export default RefDemo;
  1. 获取组件实例

    • 子组件是类组件,直接通过useRef获取子组件的实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      /*
      * @Description:
      * @Author: xiuji
      * @Date: 2024-01-26 10:34:09
      * @LastEditTime: 2024-01-26 11:06:29
      * @LastEditors: Do not edit
      */
      import React, { useEffect, useRef } from 'react';

      class SonCom extends React.Component {
      state = {
      count: 0
      }

      render() {
      const { count } = this.state;
      return (
      <div>
      <h1>子组件{count}</h1>
      </div>
      )
      }
      }

      const RefDemo = () => {
      const box = useRef(null);

      useEffect(() => {
      console.log(box.current); // 第一次渲染完毕即可获取到子组件实例(包含子组件内部状态及方法
      },[]);

      return (
      <div>
      <SonCom ref={box} />
      </div>
      )
      }

      export default RefDemo;
    • 子组件是函数组件

      直接通过useRef访问函数子组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      /*
      * @Description:
      * @Author: xiuji
      * @Date: 2024-01-26 10:34:09
      * @LastEditTime: 2024-01-26 11:11:44
      * @LastEditors: Do not edit
      */
      import React, { useEffect, useRef } from 'react';

      const SonCom = () => {
      return (
      <div>
      <h1>子组件</h1>
      </div>
      )
      }

      const RefDemo = () => {
      const box = useRef(null);

      useEffect(() => {
      console.log(box.current);
      });

      return (
      <div>
      <SonCom ref={box} />
      </div>
      )
      }

      export default RefDemo;

      控制台报错:Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

      需要通过forwardRef转发才能在父组件中获取函数子组件节点

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      const SonCom = React.forwardRef((props, ref) => {
      return (
      <div>
      <h1 ref={ref}>子组件</h1>
      </div>
      )
      })

      const RefDemo = () => {
      const box = useRef(null);

      useEffect(() => {
      console.log(box.current); // <h1>子组件</h1>
      });

      return (
      <div>
      <SonCom ref={box} />
      </div>
      )
      }

      export default RefDemo;

      这样可以获取到绑定ref的DOM节点,但无法获取到子组件内的状态及方法

      借助useImperativeHandle暴露函数子组件内的状态及方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      const SonCom = React.forwardRef((props, ref) => {
      let [count, setCount] = React.useState(0);

      const add = () => {
      setCount(count + 1);
      }

      useImperativeHandle(ref, () => {
      return { // 将需要暴露的状态和方法return出去
      count,
      setCount,
      add
      }
      })

      return (
      <div ref={ref}>
      <h1>子组件{count}</h1>
      <button onClick={add}>add</button>
      </div>
      )
      })

      const RefDemo = () => {
      const box = useRef(null);

      useEffect(() => {
      console.log(box.current);
      });

      return (
      <div>
      <SonCom ref={box} />
      </div>
      )
      }

      export default RefDemo;
  2. 保存任意可变值useRef 还可以用来保存在组件的生命周期内持续存在的任何可变值。这和 useState 不同,因为当 ref 对象的 .current 属性被修改时,不会触发组件的重新渲染。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /*
    * @Description:
    * @Author: xiuji
    * @Date: 2024-01-26 10:34:09
    * @LastEditTime: 2024-01-26 10:54:05
    * @LastEditors: Do not edit
    */
    import React, { useEffect, useRef } from 'react';

    const RefDemo = () => {
    const id = useRef(null);

    useEffect(() => {
    id.current = setInterval(() => {
    console.log('id', id.current);
    }, 1000);
    return () => {
    clearInterval(id.current);
    }
    })

    return (
    <div>
    保存任意可变值
    </div>
    )
    }

    export default RefDemo;