React 内置 Hook 之 useRef 深度解析与使用案例

前言

React Hooks 已经成为了现代 React 开发中不可或缺的一部分,今天我们来深入研究一个特别有用的 Hook —— useRef。在这篇文章中,我们将引导你了解 useRef 的运用,并通过一些实例穿插其中,让你对这个 Hook 有更深入的理解。

什么是useRef

useRef 是React的一个内置Hook,它可以返回一个可改变的ref对象,非常适合用于管理不会触发组件渲染的变量。用官方的话来说,一个常见的用例是管理不受React控制的变化(也就是说,这些变化不会引起渲染)。在本文中,我们将通过提供具体示例,来详细介绍什么是 'useRef',它的运行方式,以及如何在应用程序中使用。

**useRef**使用方式

一、基本使用方式

下面是一个简单的useRef的使用例子,它涉及通过按钮使输入元素获得焦点:

javascript
jsx复制代码 import React, { useRef } from 'react'; function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); } export default TextInputWithFocusButton;

在上面的例子中,**useRef**返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数(在这种情况下为 null)。这个值可以在整个组件的生命周期中更改,并且不会触发重新渲染。

二、实际使用方式

我们可以使用 useRef 追踪上一次的 props 或 state,以便我们可以进行比较,确定是否需要触发特定的效果。

例如,假设我们有一个自定义的 "usePrevious" Hook,它存储并返回上一次的值:

javascript
import { useEffect, useRef } from 'react'; function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); // 只有当值改变时才更新 ref return ref.current; }

然后你就可以在组件中像这样使用这个自定义Hook:

javascript
function Counter() { const [count, setCount] = useState(0); const previousCount = usePrevious(count); return ( <> <h1>{count}</h1> <h2>{previousCount}</h2> <button onClick={() => setCount(count + 1)}> Increment count </button> </> ); }

上述示例中的 usePrevious Hook 就是 useRef 的一个完美使用案例。

三、组件间共享的“实例变量”

**useRef除了用于获取DOM,还可以作为保存组件之间共享的可变值,比如你需要在组件之间共享一些数据,但又不想引起组件重新渲染,那么 useRef**将是个很好的工具。

例如,你需要在组件之间共享一个正在进行的动画帧(requestAnimationFrame ID)或定时器ID;

javascript
import React, { useRef } from 'react'; function Timer() { const timerId = useRef(null); const startTimer = () => { timerId.current = setInterval(() => console.log('Timer is running'), 1000); }; const stopTimer = () => { clearInterval(timerId.current); }; return ( <> <button onClick={startTimer}>Start Timer</button> <button onClick={stopTimer}>Stop Timer</button> </> ); } export default Timer;

在上面的例子中,我们使用**useRef**来保存intervalID引用,从而我们可以在之后的函数中清除这个定时器。

同样,如果想要在不同的效果函数(effects)之间共享变量,同时不触发重新渲染,**useRef**也是个很好的选择。

**useRef useState**的对比

useRefuseState两者都可以在组件的渲染之间保存数据。但是,useState会在状态更新时触发组件的重新渲染,而 useRef则不会。因此,如果你需要在组件的多次渲染之间保存数据,而这些数据的改变无需触发组件的更新,则可以使用 useRef

比如在我们处理表单输入并进行合法性验证时,我们可能需要定义一个值来保存上一次的表单值进行对比,但这个上次的值并不直接影响渲染,这时候我们就可以使用useRef。

useRef 设计原因

useRef 实际上是对 React 早期类组件中 ref 的一种替代。与 React 的虚拟 DOM 和声明式编程模式相比,useRef 提供了一种更直接的方式去获取和操作 DOM 节点,并且它为保持可变值的稳定性也提供了解决方案,这非常适合在函数组件中使用。

本质上,useRef 是 React 对直接操作 DOM 的策略的别一种尝试。它既遵循了 React 的工作模式,又满足了在某些情况下我们需要直接访问和操作 DOM 的需求。

注意事项

在使用 useRef 的过程中,有些注意事项需要特别注意,如:

  • 当你在组件渲染中改变 useRef 的值,React 不会触发重新渲染。相反地,当你改变通过 useState 创建的state,React 会立即计划重新渲染。
  • 向 DOM ref 添加一个值可能会引发副作用。假设你有一个清单项组件,它有一个拖拽功能。可能你需要使用 useRef 来创建一个 ref,并绑定到清单项的 DOM 节点上。这样你就可以在事件处理程序中访问这个节点。但是,这种方法会绕过 React 的渲染机制直接操作DOM,可能会导致一些不可预知的问题。

总结

总的来说,useRef 是我们在构建 React 应用时非常**useRef** 是一个非常有用的 Hook,它能够在组件的全部声明周期内保持和跟踪改变的数据。然而,它不会导致组件重新渲染,使得在特定场景(如当改变的数据并不直接影响渲染或改变的数据需要在庞大的组件树间传递时)有着重要的作用。

总的来说,理解和掌握如何灵活使用useRef,能帮助我们编写出更加强大、可维护的React应用。