我在“ Hooks FAQ”上阅读了关于React useState()
和useRef()
的信息,我对一些似乎同时具有useRef和useState解决方案的用例感到困惑,我不确定哪种方法正确。
从“ Hooks常见问题解答”中about useRef():
“ useRef()钩不仅用于DOM引用。“ ref”对象是通用容器,其当前属性是可变的,并且可以保存任何值,类似于类的实例属性。”
使用 useRef():
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
使用 useState():
function Timer() {
const [intervalId, setIntervalId] = useState(null);
useEffect(() => {
const id = setInterval(() => {
// ...
});
setIntervalId(id);
return () => {
clearInterval(intervalId);
};
});
// ...
}
两个示例的结果相同,但是哪一个更好-为什么?
答案 0 :(得分:5)
两者之间的主要区别是:
useState
导致重新渲染,useRef
导致重新渲染。
它们之间的共同点是,useState
和useRef
都可以在重新渲染后记住它们的数据。因此,如果您的变量是决定视图图层渲染的变量,请使用useState
。否则使用useRef
我建议您阅读此article。
答案 1 :(得分:2)
useRef
在您想要跟踪值更改但不想触发重新渲染或useEffect
时很有用。
大多数用例是当您有一个依赖于值的函数时,但该值需要由函数结果本身更新。
例如,假设您要对某些 API 结果进行分页:
const [filter, setFilter] = useState({});
const [rows, setRows] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const fetchData = useCallback(async () => {
const nextPage = currentPage + 1;
const response = await fetchApi({...filter, page: nextPage});
setRows(response.data);
if (response.data.length) {
setCurrentPage(nextPage);
}
}, [filter, currentPage]);
fetchData
正在使用 currentPage
状态,但它需要在成功响应后更新 currentPage
。这是不可避免的过程,但它很容易在 React 中导致无限循环又名 Maximum update depth exceeded error
。例如,如果您想在加载组件时获取行,则需要执行以下操作:
useEffect(() => {
fetchData();
}, [fetchData]);
这是有问题的,因为我们使用状态并在同一个函数中更新它。
我们想要跟踪 currentPage
,但不想通过其更改触发 useCallback
或 useEffect
。
我们可以用useRef
轻松解决这个问题:
const currentPageRef = useRef(0);
const fetchData = useCallback(async () => {
const nextPage = currentPageRef.current + 1;
const response = await fetchApi({...filter, page: nextPage});
setRows(response.data);
if (response.data.length) {
currentPageRef.current = nextPage;
}
}, [filter]);
我们可以在 currentPage
的帮助下从 useCallback
deps 数组中删除 useRef
依赖项,因此我们的组件免于无限循环。
答案 2 :(得分:1)
基本上,在这种情况下,我们应使用 UseState (状态),并应通过重新渲染来更新状态值。
当您希望信息在组件的整个生命周期中都保持不变时,您可以使用 UseRef ,因为它不适用于重新渲染。
答案 3 :(得分:1)
如果你存储了间隔 id,你唯一能做的就是结束这个间隔。更好的是存储状态 timerActive
,以便您可以在需要时停止/启动计时器。
function Timer() {
const [timerActive, setTimerActive] = useState(true);
useEffect(() => {
if (!timerActive) return;
const id = setInterval(() => {
// ...
});
return () => {
clearInterval(intervalId);
};
}, [timerActive]);
// ...
}
如果您希望回调在每次渲染时更改,您可以使用 ref 来更新每次渲染的内部回调。
function Timer() {
const [timerActive, setTimerActive] = useState(true);
const callbackRef = useRef();
useEffect(() => {
callbackRef.current = () => {
// Will always be up to date
};
});
useEffect(() => {
if (!timerActive) return;
const id = setInterval(() => {
callbackRef.current()
});
return () => {
clearInterval(intervalId);
};
}, [timerActive]);
// ...
}