React ref & useRef 完全指南

在这里插入图片描述

在这篇文章中,你将学习如何使用React.useRef()钩子来创建持久的可变值(也称为references或refs),以及访问DOM元素。

我们将从下面几点讲解:

1. 可变值
- 1.1用例:记录按钮点击
- 1.2用例:实现秒表

2. 访问DOM元素
- 2.1用例:聚焦输入

3.更新引用限制
4. 总结

可变值

useRef(initialValue)接受一个参数(引用的初始值)并返回一个引用(也称为ref)。引用只是一个具有特殊属性current的对象:

const reference = useRef(initialValue);

reference.current; // 当前的引用

reference.current 访问引用,并且 reference.current = newValue 更新引用值:

import { useRef } from 'react';

function MyComponent() {
  const reference = useRef(initialValue);

  const someHandler = () => {
    // 访问引用
    const value = reference.current;
    // 更新引用值
    reference.current = newValue;
  };
}

关于 references 有两点需要记住:

  1. 在组件重新渲染之间,引用的值是持久化的(保持不变);
  2. 更新引用不会触发组件重新呈现。

现在,让我们看看如何在实践中使用 useRef()

实例:记录按钮点击

组件logbuttonclicked使用了一个引用来存储按钮的点击次数:

import { useRef } from 'react';

function LogButtonClicks() {
  const countRef = useRef(0);
  
  const handle = () => {
    countRef.current++;
    console.log(`Clicked ${countRef.current} times`);
  };

  console.log('I rendered!');

  return <button onClick={handle}>Click me</button>;
}

const countRef = useRef(0)创建一个用0初始化的引用countRef

当按钮被单击时,handle函数被调用,并且引用值被递增:countRef.current++,该引用值被记录到控制台。

注意,更新引用值countRef.current++不会触发组件重新渲染。
'I rendered!'在初始渲染时只会输出一次。

现在有一个合理的问题:引用和状态之间的主要区别是什么?

现在有一个合理的问题:referencesstate之间的主要区别是什么?

reference 和 state 之间的主要区别

让我们重用上一节中的logbuttonclicked组件,但使用useState()钩子来计算按钮的点击次数:

import { useState } from 'react';

function LogButtonClicks() {
  const [count, setCount] = useState(0);
  
  const handle = () => {
    const updatedCount = count + 1;
    console.log(`Clicked ${updatedCount} times`);
    setCount(updatedCount);
  };

  console.log('I rendered!');

  return <button onClick={handle}>Click me</button>;
}

每次点击,你会在控制台中看到“I rendering !”’——这意味着每次状态更新时,组件都会重新呈现

所以,state和references之间的两个主要区别是:

  1. 更新 state 会触发组件重新呈现,而更新 ref 则不会。
  2. state 更新是异步的(state变量在重新呈现后更新),而ref则同步更新(更新后的值立即可用)

从更高的角度来看,ref 用于存储组件的基础设施数据,而 state 存储直接呈现在屏幕上的信息

实例:实现秒表

你可以存储在 ref 中的东西是涉及到一些副作用的基础设施信息。例如,你可以在ref中存储不同类型的指针:定时器id,套接字id,等等。

例如,下面的秒表组件使用setInterval(回调,时间)计时器函数来增加秒表计数器的每一秒。定时器id存储在一个引用timerIdRef:

import { useRef, useState, useEffect } from 'react';

function Stopwatch() {
  const timerIdRef = useRef(0);
  const [count, setCount] = useState(0);

  const startHandler = () => {
    if (timerIdRef.current) { return; }
    timerIdRef.current = setInterval(() => setCount(c => c+1), 1000);
  };

  const stopHandler = () => {
    clearInterval(timerIdRef.current);
    timerIdRef.current = 0;
  };

  useEffect(() => {
    return () => clearInterval(timerIdRef.current);
  }, []);

  return (
    <div>
      <div>Timer: {count}s</div>
      <div>
        <button onClick={startHandler}>Start</button>
        <button onClick={stopHandler}>Stop</button>
      </div>
    </div>
  );
}

startHandler()函数在单击Start按钮时调用,它启动计时器并在引用timerIdRef.current= setInterval(…)中保存计时器id。

要停止秒表,请单击“停止”按钮。停止按钮处理程序stopHandler()从引用中访问计时器id并停止计时器clearInterval(timerIdRef.current)

此外,如果组件在秒表处于活动状态时卸载,useEffect()的清理函数也将停止计时器。

在秒表示例中,ref用于存储基础架构数据—活动计时器id。

访问 DOM 元素

useRef()钩子的另一个有用的应用是访问DOM元素。这需要三个步骤:

  1. 定义访问元素const elementRef = useRef()的引用;

  2. 将引用赋给元素的ref属性:<div ref={elementRef}></div>;

  3. 引用完成后,elementRef.current包含DOM元素。

import { useRef, useEffect } from 'react';

function AccessingElement() {
  const elementRef = useRef();

   useEffect(() => {
    const divElement = elementRef.current;
  }, []);

  return (
    <div ref={elementRef}>
      I'm an element
    </div>
  );
}

实例:聚焦输入框

import { useRef, useEffect } from 'react';

function InputFocus() {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <input 
      ref={inputRef} 
      type="text" 
    />
  );
}

const inputRef = useRef()创建一个引用来保存输入元素。

然后将inputRef赋值给输入字段的ref属性:<input ref={inputRef} type="text" />

然后,设置inputRef 作为输入元素。现在您可以通过编程方式将焦点设置为输入状态:inputRef.current.focus()

在初始化渲染时 Ref 是 null

在初始渲染时,保存DOM元素的 ref 是空的:

import { useRef, useEffect } from 'react';

function InputFocus() {
  const inputRef = useRef();

  useEffect(() => {
    // 输出 `HTMLInputElement` 
    console.log(inputRef.current);

    inputRef.current.focus();
  }, []);

  // 初始化渲染时输出 `undefined` 
  console.log(inputRef.current);

  return <input ref={inputRef} type="text" />;
}

在初始渲染期间,React仍然决定组件的输出,因此还没有创建DOM结构。这就是为什么inputRef。current在初始呈现时计算为undefined

当输入元素在DOM中创建完成后,useEffect(callback,[])钩子立即调用回调函数:因此回调函数是访问inputRef.current的正确位置。

更新 references 限制

功能组件的功能范围应该计算输出或调用钩子。

这就是为什么更新 ref (以及更新 state)不应该在组件函数的直接作用域内执行。

ref必须在useEffect()回调或处理程序(事件处理程序、计时器处理程序等)内部更新

import { useRef, useEffect } from 'react';

function MyComponent({ prop }) {
  const myRef = useRef(0);

  useEffect(() => {
    myRef.current++; // Good!

    setTimeout(() => {
      myRef.current++; // Good!
    }, 1000);
  }, []);

  const handler = () => {
    myRef.current++; // Good!
  };

  myRef.current++; // Bad!

  if (prop) {
    myRef.current++; // Bad!
  }

  return <button onClick={handler}>My button</button>;
}

总结

useRef()钩子存储可变的值(又名references或refs),这些值在渲染之间持久化,也可以访问DOM元素。

使用初始值调用const reference = useRef(initialValue)会返回一个名为reference的特殊对象。引用对象有一个属性current:可以使用该属性读取引用值,或更新引用。reference.current = newValue

在组件重新呈现之间,引用的值是持久的。

更新引用与更新状态相反,不会触发组件重新呈现。

引用也可以访问DOM元素。<div ref={reference}> element </div> - 元素在reference.current中是可用的。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程轨迹_

期望和你分享一杯咖啡

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

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

打赏作者

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

抵扣说明:

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

余额充值