在这篇文章中,你将学习如何使用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
有两点需要记住:
- 在组件重新渲染之间,引用的值是持久化的(保持不变);
- 更新引用不会触发组件重新呈现。
现在,让我们看看如何在实践中使用 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!'在初始渲染时只会输出一次。
现在有一个合理的问题:引用和状态之间的主要区别是什么?
现在有一个合理的问题:references
和state
之间的主要区别是什么?
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之间的两个主要区别是:
- 更新 state 会触发组件重新呈现,而更新 ref 则不会。
- 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元素。这需要三个步骤:
- 定义访问元素
const elementRef = useRef()
的引用; - 将引用赋给元素的
ref
属性:<div ref={elementRef}></div>
; - 引用完成后,
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
中是可用的。