React.useRef 是 React Hooks 中的一种,它提供了一种可以在函数组件中存储可变值的方式。与 useState 不同,useRef 存储的值不会引起组件的重新渲染。为什么要有 useRef?我们使用 useRef 主要有以下几个原因:
- 保存 DOM 元素的引用:在函数组件中,我们无法像类组件中那样直接使用 this 来获取 DOM 元素的引用。而 useRef 可以用来保存 DOM 元素的引用,方便我们获取或修改其属性。
- 保存组件的状态:在函数组件中,每次组件重新渲染时,所有的变量都会被重新声明和初始化。为了在多次渲染之间保留一些数据,我们可以使用 useRef 来保存这些数据,它们在组件的整个生命周期中保持不变。
- 避免重新渲染的性能问题:在某些情况下,我们需要保存一些数据,但是这些数据并不需要触发重新渲染。如果使用 useState,每次更新这些数据都会触发组件的重新渲染,从而浪费性能。而使用 useRef 可以避免这个问题。
- 在组件之间传递数据:在函数组件中,我们可以使用 useContext 或 useReducer 等钩子来在组件之间传递数据。但是有些情况下,我们只需要简单地在组件之间传递一个变量或者一个函数,此时使用 useRef 可以更加方便。
代码示例
使用 useRef 存储可变值:
import React, { useRef } from 'react';function App() { const counterRef = useRef(0); function handleClick() { counterRef.current += 1; console.log(counterRef.current); } return ( <button onClick={handleClick}> Click me </button> );}在这个例子中,我们使用 useRef 创建了一个名为 counterRef 的变量,并初始化为 0。每次按钮被点击时,我们都会将 counterRef.current 的值加 1,并将结果打印到控制台中。
使用 useRef 存储 DOM 元素的引用:
import React, { useRef } from 'react';function App() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input type="text" ref={inputRef} /> <button onClick={handleClick}> Focus input </button> </> );}在这个例子中,我们使用 useRef 创建了一个名为 inputRef 的变量,并将其赋值为 null。在组件中,我们将 input 元素的 ref 属性设置为 inputRef。每次按钮被点击时,我们调用 inputRef.current.focus() 来将输入框聚焦。
useRef 的特点
- 会返回一个可变的 ref 对象。
- 可以保存任何可变值,类似于在 class 组件中使用实例变量。
- 返回的 ref 对象在组件的整个生命周期中保持不变。
- 并不会在每次组件渲染时都生成新的 ref 对象,因此可以用来保存一些不需要触发重新渲染的数据。
- 可以用来引用 DOM 元素,用于获取或修改其属性。
- 可以用来在函数组件之间传递数据。
- 可以模拟实例变量,用于保存函数组件中的状态。
useRef 和 useState 的区别
useRef 和 useState 有以下几个区别:
- useRef 返回的是一个可变的 ref 对象,而 useState 返回的是一个可变的 state 值和一个更新 state 的函数。
- useRef 主要用于保存一个可变值,并不会触发组件重新渲染,而 useState 能够触发组件重新渲染。
- useRef 可以在组件渲染的过程中保持数据的稳定,而 useState 每次渲染都会重新计算 state 值。
- useRef 可以用于访问 DOM 元素,而 useState 不能。
使用场景
具体使用场景和每个场景下的代码示例:
- 存储定时器的 ID:定时器是 JavaScript 中常见的一种异步操作。在函数组件中,我们可以使用 useRef 存储定时器的 ID,以便在组件卸载时清除定时器。
import React, { useState, useEffect, useRef } from 'react';function App() { const [count, setCount] = useState(0); const intervalRef = useRef(null); useEffect(() => { intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(intervalRef.current); }, []); return ( <div> <p>{count}</p> <button onClick={() => clearInterval(intervalRef.current)}> Stop timer </button> </div> );}在这个例子中,我们使用 useRef 创建了一个名为 intervalRef 的变量,并将其初始化为 null。在 useEffect 中,我们使用 setInterval 创建了一个定时器,并将其 ID 存储在 intervalRef.current 中。在组件卸载时,我们使用 clearInterval 来清除定时器。点击 Stop timer 按钮时,我们也会使用 clearInterval 来停止定时器。
- 存储上一次的 props 或 state 值:有时候我们需要在组件更新时和之前的 props 或 state 进行比较。在这种情况下,我们可以使用 useRef 存储上一次的值。
import React, { useState, useEffect, useRef } from 'react';function App() { const [count, setCount] = useState(0); const prevCountRef = useRef(null); useEffect(() => { prevCountRef.current = count; }); const prevCount = prevCountRef.current; return ( <div> <p>Current count: {count}</p> {prevCount && ( <p>Previous count: {prevCount}</p> )} <button onClick={() => setCount(c => c + 1)}> Increase count </button> </div> );}在这个例子中,我们使用 useRef 创建了一个名为 prevCountRef 的变量,并将其初始化为 null。在 useEffect 中,我们将 count 的值存储在 prevCountRef.current 中。在组件更新时,我们通过 prevCountRef.current 获取上一次的 count 值,并将其展示在页面上。每次 count 更新时,prevCountRef.current 的值也会被更新。
- 存储 DOM 元素的引用:在函数组件中,我们无法直接使用类组件中的 this.refs。因此,我们可以使用 useRef 存储 DOM 元素的引用,以便进行 DOM 操作。
import React, { useRef } from 'react';function App() { const inputRef = useRef(null); function handleFocus() { inputRef.current.style.backgroundColor = 'yellow'; } function handleBlur() { inputRef.current.style.backgroundColor = 'white'; } return ( <> <input type="text" ref={inputRef} onFocus={handleFocus} onBlur={handleBlur} /> <button onClick={() => inputRef.current.focus()}> Focus input </button> </> );}在这个例子中,我们使用 useRef 创建了一个名为 inputRef 的变量,并将其初始化为 null。在组件中,我们将 input 元素的 ref 属性设置为 inputRef,并为 onFocus 和 onBlur 事件分别添加了 handleFocus 和 handleBlur 函数。在 handleFocus 函数中,我们将输入框的背景颜色设置为黄色,在 handleBlur 函数中将其设置为白色。点击 Focus input 按钮时,我们使用 inputRef.current.focus() 让输入框聚焦。
结语
React.useRef 提供了一种在函数组件中存储可变值和 DOM 元素引用的方法。使用 useRef 可以让我们在函数组件中实现更多的功能,并且不会引起组件的重新渲染。在使用 useRef 时,需要注意不要滥用 useRef,只在必要的时候使用它,以避免造成代码的混乱。