【React】useRef、forwardRef 、useImperativeHandle

Ref

使用 ref 引用值 – React 中文文档

希望组件“记住”某些信息,但又不想让这些信息更新时 触发新的渲染 时,可以使用 ref

也就是说 ref 对象 包裹的值 React 追踪不到的,他像是用来存储组件信息的秘密“口袋”。

与 state 相同的是,React 会在每次重新渲染组件之间保留 ref。与 state 不同的是,设置 state 会重新渲染组件,更改 ref 不会!ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current 属性(也就是我们初始化的值)。

下面是一个计时器的示例:

需要追踪按下“开始”按钮的时间和当前时间,并用于视图渲染,因此保存在 state 中。

而取消现有的 interval,让它停止更新 now state 变量。需要为其提供 interval ID, 由于 interval ID 不用于渲染,可以将其保存在 ref 中

import {useRef, useState} from "react";

function App() {
    const [startTime, setStartTime] = useState(null)
    const [now, setNow] = useState(null)
    const intervalRef = useRef(null);

    function handleStart() {
        // 设置开始时间和 当前时间
        setStartTime(Date.now())
        setNow(Date.now())
        // 清除之前可能存在的定时器
        clearInterval(intervalRef.current)
        intervalRef.current = setInterval(() => {
            setNow(Date.now())
        }, 10)
    }
    function handleStop () {
        clearInterval(intervalRef.current)
    }
    let secondsPassed = 0
    if (startTime && now) {
        secondsPassed = (now - startTime) / 1000
    }
    return (
        <div className="App">
            <h1>时间计时器,时间过去了:{secondsPassed.toFixed(3)}s</h1>
            <button onClick={handleStart}>开始</button>
            <button onClick={handleStop}>停止</button>
        </div>
    );
}

export default App;

image.png

ref 和 state 的区别:

refstate
useRef(initialValue)返回 { current: initialValue }useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue])
更改时不会触发重新渲染更改时触发重新渲染。
可变 —— 你可以在渲染过程之外修改和更新 current 的值。“不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。
你不应在渲染期间读取(或写入) current 值。你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照

如果将 useRef的值用于 试图渲染,将会发生什么呢?

可以看到值虽然改变了,但是视图并没有同步更新。

import React, {useRef} from 'react';

function ChildCom1(props) {

    let countRef = useRef(0);

    function handleClick() {
        // 这样并未重新渲染组件!
        countRef.current = countRef.current + 1;
        console.log(countRef.current)
    }

    return (
        <>
            <h1>这是子组件</h1>
            <button onClick={handleClick}>
                你点击了 {countRef.current} 次
            </button>
        </>
    );
}

export default ChildCom1;

image.png

ref 的使用场景:

如果组件需要存储一些值,但不影响渲染逻辑,选择 ref。

state 就像 每次渲染的快照,并且 不会同步更新。但是当你改变 ref 的 current 值时,它会立即改变。

forwardRef

默认情况下,组件不会将它们的 DOM 节点暴露给父组件。举例来说,如果你想要 MyInput 的父组件 能访问到<input>DOM 节点,你必须选择使用 forwardRef 。

访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。需要一个指向 DOM 节点的 ref 来实现。

import React, {useRef} from 'react';

function ChildCom2(props) {
    const inputRef = useRef(null)
    function handleClick() {
        inputRef.current.focus()
    }
    return (
        <div>
            <h1>这是子组件2</h1>
            <input ref={inputRef} />
            <button onClick={handleClick}>
                聚焦输入框
            </button>
        </div>
    );
}

export default ChildCom2;

image.png

注意:React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!

import { useRef } from 'react';

function MyInput(props) {
  return <input {...props} />;
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

image.png

image.png

想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件,使用 forwardRef API。

const MyInput = forwardRef((props, ref) => {
    return <input {...props} ref={ref} />;
});

image.png

React 中,每次更新都分为 两个阶段

  • 渲染 阶段, React 调用你的组件来确定屏幕上应该显示什么。
  • 提交 阶段, React 把变更应用于 DOM。

在第一次渲染期间,DOM 节点尚未创建,因此 ref.current 将为 null。在渲染更新的过程中,DOM 节点还没有更新,所以也不应该在此刻读取 ref。

更新 DOM 后,React 立即将它们设置到相应的 DOM 节点。

切换 dom 节点可以使用 条件渲染state 切换它的显示和隐藏。而ref.current.remove();可以将节点直接从 DOM 中删除。

useImperativeHandle

自定义向父组件要暴露的东西。

实现类似于 Vue 中 expose 的功能。

示例:假设不想暴露出整个 <input> DOM 节点,但你想要它其中的方法:focus 。为此,用单独额外的 ref 来指向真实的浏览器 DOM。然后使用 useImperativeHandle 来暴露一个方法 。

示例:

import {forwardRef, useImperativeHandle, useRef} from 'react';

// function MyInput(props) {
//   return <input {...props} />;
// }

const MyInput = forwardRef((props, ref) => {
    const inputElement = useRef(null)
    // 自定义向父组件要暴露的东西
    // 第一个参数 ref 是父组件传递过来的 ref
    // 第二个参数 是一个函数,返回一个对象,这个对象就是父组件要暴露的东西
    useImperativeHandle(ref, () => ({
        focus() {
            if (inputElement.current) {
                inputElement.current.focus(); // 调用<input>元素的focus方法
            }
            console.log('使用useImperativeHandle,默认不暴露组件自身的focus方法,想暴露需要重新写一个focus方法');
        },
    }))
    return <input {...props} ref={inputElement}/>;
})

export default function MyForm() {
    const inputRef = useRef(null);

    function handleClick() {
        inputRef.current.focus();
    }

    return (
        <>
            <MyInput ref={inputRef}/>
            <button onClick={handleClick}>
                聚焦输入框
            </button>
        </>
    );
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秀秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值