目前来说,因为函数组件每次触发更新时,都会重新运行。无法像类组件一样让一些内容保持不变。
所以才出现了各种 HOOK 函数:useState
,useCallback
,useMemo
等来辅助实现和类组件相似的功能。
useRef
也是这样的目的。
在之前的文章中介绍了 ref
,用于获取组件或真实DOM元素的引用。
而 useRef
作用相同,不过可以在函数组件中使用。同时它会返回对象的固定引用。
换句话说,当函数组件重新运行时,
useRef()
前后2次返回的对象引用地址相同。
一个节点(React元素)对应一个唯一的对象。
React.createRef()
使用举例:
import React, { useState } from "react";
export default function App() {
const refInput = React.createRef();
return (
<>
<input ref={refInput}></input>
<button
onClick={() => {
console.log(refInput.current.value);
}}
>
获取 inputRef
</button>
</>
);
}
换成 useRef
仅需要替换一行代码:
const refInput = React.createRef();
// 替换为
const refInput = useRef();
上面的代码中,看起来只是一行代码的区别,但本质上处理逻辑不同。
React.createRef()
,如果 ref
的值发生变动(函数组件重新渲染),则将旧值设为 null
;useRef()
,函数组件重新渲染多次时,所有返回的对象的引用地址相同。验证下:
简单修改下,将每次更新后的新 ref
放到 window
对象中对比下:
import React, { useRef, useState } from "react";
window.arr = [];
export default function App() {
const refInput = React.createRef();
window.arr.push(refInput);
const [n, setN] = useState(); // 只是为了重新渲染组件。
return (
<>
<input
ref={refInput}
type="text"
value={n}
onChange={(e) => {
setN(e.target.value);
}}
/>
</>
);
}
多次改变 input.value
时,检查 window.arr
:
替换为 const refInput = useRef();
在之前介绍 useEffect
时,提到了下面的写法是有问题的。
因为 useEffect
只会执行一次,所以在计时器中通过闭包获取的状态变量 n 永远都是10,
export default function App() {
const [n, setN] = useState(10);
useEffect(() => {
const timer = setInterval(() => {
setN(n - 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>{n}</div>;
}
该问题,通过将依赖项 n 传入即可。但会引起另一个问题:
每次函数重新运行,都会再次执行 useEffect
,开启计时器又销毁计时器,开销很大。
export default function App() {
const [n, setN] = useState(10);
useEffect(() => {
const timer = setInterval(() => {
setN(n - 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, [n]);
return <div>{n}</div>;
}
所以,可通过 useRef
来将函数重新运行后的新值传递给计时器,同时 useEffect
也只会运行一次,开启一次计时器!
注意,
useRef()
返回的虽然是同一个对象,但setN
修改的是它的current
属性。所以计时器每次获取的都是新值。
export default function App() {
const [n, setN] = useState(10);
const refN = useRef(n);
useEffect(() => {
const timer = setInterval(() => {
setN(--refN.current); // 修改的是 n
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>{n}</div>;
}
以上。