在这篇文章中,你将学习如何使用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
之间的主要区别是什么?
让我们重用上一节中的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之间的两个主要区别是:
从更高的角度来看,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。
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()
。
在初始渲染时,保存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
的正确位置。
功能组件的功能范围应该计算输出或调用钩子。
这就是为什么更新 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
中是可用的。