React中的Ref和State:何时使用哪一个?

210 阅读6分钟

前言

在React中,状态管理和副作用处理是交互式UI的基石。useState和useRef是React提供的两个Hooks,它们都可以用来在函数组件中保存和管理数据,但它们的用途和行为有所不同。本文将探讨ref和state的区别,并提供一些案例。

Ref的使用

useRef是一个Hook,它返回一个可变的ref对象,其.current属性用于存储任何值。这个值在组件的整个生命周期内都是持久的,不会随着组件的重新渲染而改变。这使得ref成为存储DOM元素引用、文本内容、定时器ID等不需要触发组件重新渲染的状态的理想选择。

如何使用useRef

给你的组件添加ref

你可以通过从 React 导入 useRef Hook 来为你的组件添加一个 ref:

import { useRef } from 'react';

在你的组件内,调用 useRef Hook 并传入你想要引用的初始值作为唯一参数。例如,这里的 ref 引用的值是“0”:

const ref = useRef(0);

useRef 返回一个这样的对象:

{ current: 0 // 你向 useRef 传入的值 }

我们可以用 ref.current 属性访问该 ref 的当前值。这个值是有意被设置为可变的,既可以读取它也可以写入它。就像一个 React 追踪不到的、用来存储组件信息的秘密“口袋”。

例如该示例,每次点击按钮时会使 ref.current 递增:

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('你点击了 ' + ref.current + ' 次!');
  }

  return (
    <button onClick={handleClick}>
      点击
    </button>
  );
}

这里的 ref 指向一个数字,但是,像 state 一样,你可以让它指向任何东西:字符串、对象,甚至是函数。与 state 不同的是,ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current 属性。

请注意,组件不会在每次递增时重新渲染。 与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!

Ref与普通变量的区别

ref和普通变量虽然都可以用来存储数据,但它们之间有几个关键的区别,特别是在React组件的上下文中使用时。下面,我们来详细探讨这些区别:

  1. 持久性:
  • ref:通过useRef创建的ref对象在组件的整个生命周期内保持不变,其.current属性的值在组件重新渲染时不会丢失。
  • 普通变量:每次组件重新渲染时,普通变量会被重新初始化。
  1. 可变性:
  • ref:ref的.current属性是可变的,可以在组件的任何阶段被读取和修改。
  • 普通变量:同样可变,但在函数重新执行时会重置为初始值。
  1. 作用域:
  • ref:在组件的顶层定义,因此在整个组件内都是可访问的。
  • 普通变量:通常限制在定义它们的函数或代码块内。

在这段代码中我们可以看到在每次组件渲染时,普通变量commonCount都会被重新初始化,ref对象在组件的整个生命周期内保持不变。

import { useRef, useState } from 'react'

function App() {
  let refCount = useRef(0)
  const [count, setCount] = useState(0)
  function handleClick() {
    refCount.current = refCount.current + 1
    console.log('refCount:', refCount.current);

    setCount(count + 1)
    console.log('stateCount', count);

  }

  return (
    <>
      <button onClick={handleClick}>+1</button>
      <p>refCount: {refCount.current}</p>
      <p>stateCount: {count}</p>
    </>
  )
}

export default App

image.png

在这段代码中可以看到,使用useRef定义的变量在组件渲染时不会重新初始化

Ref与State结合使用

示例:秒表的实现

在单个组件中把 ref 和 state 结合起来使用。例如,让我们制作一个秒表,用户可以通过按按钮来使其启动或停止。为了显示从用户按下“开始”以来经过的时间长度,你需要追踪按下“开始”按钮的时间和当前时间。此信息用于渲染,所以把它保存在 state 中:

const [startTime, setStartTime] = useState(null);

const [now, setNow] = useState(null);

当用户按下“开始”时,将用 setInterval 每 10 毫秒更新一次时间:

当按下“停止”按钮时,需要取消现有的 interval,以便让它停止更新 now state 变量。可以通过调用 clearInterval 来完成此操作。但需要为其提供 interval ID,此 ID 是之前用户按下 Start、调用 setInterval 时返回的。需要将 interval ID 保留在某处。 由于 interval ID 不用于渲染,可以将其保存在 ref 中:

import { useState, useRef } from 'react';

export default function Stopwatch() {
  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 != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>时间过去了: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        开始
      </button>
      <button onClick={handleStop}>
        停止
      </button>
    </>
  );
}

当一条信息用于渲染时,将它保存在 state 中。当一条信息仅被事件处理器需要,并且更改它不需要重新渲染时,使用 ref 可能会更高效。

Ref与State的区别

  • 触发渲染:useState的更新会触发组件的重新渲染,而ref的更新不会。
  • 可变性:ref是可变的,你可以直接修改.current属性。useState则需要通过更新函数来修改状态,这是不可变的。
  • 用途:ref适用于不需要触发渲染的数据,如DOM引用或大型数据结构。state适用于需要响应式更新的数据,如表单输入或计数器。

看到这里你可能会好奇useRef那么强大,可以持久化的保留变量的值而不会随组件的渲染而被再次初始化,那么useRef到底是咋实现的呢?

useRef的运行机制

尽管 useState 和 useRef 都是由 React 提供的, useRef 可以在 useState 的基础上 实现。可以想象在 React 内部,useRef 是这样实现的:

// React 内部
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}

第一次渲染期间,useRef 返回 { current: initialValue }。 该对象由 React 存储,因此在下一次渲染期间将返回相同的对象。 请注意,在这个示例中,state 设置函数没有被用到。它是不必要的,因为 useRef 总是需要返回相同的对象!

总结

ref和state都是React中管理数据的重要工具,但它们的用途和行为有所不同。ref适用于保存不触发渲染的数据,而state适用于需要响应式更新的数据。了解它们的区别并遵循最佳实践,可以帮助你构建更可预测和高效的React组件。

本篇文章就到此结束了,希望对大家能有所帮助,如果觉得本篇文章对你有所还请点赞+收藏+评论,谢谢大家。

image.png