提问人:Haim763 提问时间:6/5/2019 最后编辑:YilmazHaim763 更新时间:5/29/2023 访问量:65633
反应:useState 还是 useRef?
React: useState or useRef?
问:
我正在阅读有关 React 和“Hooks FAQ”的信息,我对一些似乎同时具有 useRef 和 useState 解决方案的用例感到困惑,我不确定哪种方式是正确的。useState()
useRef()
从关于 useRef() 的 “Hooks FAQ” 中:
“useRef() Hook 不仅适用于 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);
};
});
// ...
}
两个例子都会有相同的结果,但哪一个更好 - 为什么?
答:
两者之间的主要区别在于:
useState
导致重新渲染,则不然。useRef
它们之间的共同点是,两者都可以在重新渲染后记住它们的数据。因此,如果您的变量决定了视图图层渲染,请使用 。否则使用useState
useRef
useState
useRef
我建议阅读这篇文章。
评论
基本上,我们在这些情况下使用 UseState,在这种情况下,状态的值应该通过重新渲染来更新。
当您希望您的信息在组件的生命周期内保留时,您将使用 UseRef,因为它不适用于重新渲染。
如果存储间隔 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]);
// ...
}
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
正在使用状态,但需要在成功响应后进行更新。这是不可避免的过程,但它很容易导致无限循环,也就是 React 中的无限循环。例如,如果要在加载组件时获取行,则需要执行如下操作:currentPage
currentPage
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]);
我们可以借助 从 deps 数组中删除依赖项,因此我们的组件从无限循环中保存下来。currentPage
useCallback
useRef
您还可以使用 ref dom 元素(默认 HTML 属性)useRef
例如:分配一个按钮以专注于输入字段。
而仅更新值并重新呈现组件。useState
这实际上主要取决于您使用计时器的目的,这尚不清楚,因为您没有显示组件渲染的内容。
如果要在组件的渲染中显示计时器的值,则需要使用 useState。否则,更改 ref 的值不会导致重新渲染,并且计时器不会在屏幕上更新。
如果必须发生其他事情,这应该在计时器的每个刻度上直观地更改 UI,请使用 useState 并将 timer 变量放在 useEffect 钩子的依赖项数组中(您可以在其中执行 UI 更新所需的任何操作),或者根据计时器值在 render 方法(组件返回值)中执行逻辑。 SetState 调用将导致重新渲染,然后调用 useEffect 钩子(取决于依赖项数组)。 使用 ref 时,不会发生任何更新,也不会调用 useEffect。
如果您只想在内部使用计时器,则可以改用 useRef。每当必须发生导致重新渲染的事情时(即在一段时间过去后),您就可以从 setInterval 回调中调用另一个带有 setState 的状态变量。然后,这将导致组件重新渲染。
只有在真正必要时(即在出现流程或性能问题的情况下),才应该对本地状态使用 refs,因为它不遵循“React 方式”。
- 要查看的计数器应用不会重新呈现
useRef
如果使用 useRef 创建一个简单的计数器应用来存储状态:
import { useRef } from "react";
const App = () => {
const count = useRef(0);
return (
<div>
<h2>count: {count.current}</h2>
<button
onClick={() => {
count.current = count.current + 1;
console.log(count.current);
}}
>
increase count
</button>
</div>
);
};
如果单击该按钮,则此值不会更改,因为组件不会重新渲染。如果您检查控制台,您将看到该值实际上正在增加,但由于组件没有重新渲染,因此 UI 不会更新。 <h2>count: {count.current}</h2>
console.log(count.current)
如果使用 设置状态,请单击该按钮将重新呈现组件,以便更新 UI。useState
- 防止在键入 时进行不必要的重新渲染。
input
重新渲染是一项代价高昂的操作。在某些情况下,您不希望继续重新呈现应用程序。例如,当您将输入值存储在状态中以创建受控组件时。在这种情况下,对于每次击键,您都需要重新呈现应用。如果使用 the 获取对 DOM 元素的引用,则只会重新渲染组件一次:ref
useState
import { useState, useRef } from "react";
const App = () => {
const [value, setValue] = useState("");
const valueRef = useRef();
const handleClick = () => {
console.log(valueRef);
setValue(valueRef.current.value);
};
return (
<div>
<h4>Input Value: {value}</h4>
<input ref={valueRef} />
<button onClick={handleClick}>click</button>
</div>
);
};
- 防止内部无限循环
useEffect
要创建一个简单的翻转动画,我们需要 2 个状态值。一个是要在间隔内翻转或不翻转的布尔值,另一个是当我们离开组件时清除订阅:
const [isFlipping, setIsFlipping] = useState(false);
let flipInterval = useRef<ReturnType<typeof setInterval>>();
useEffect(() => {
startAnimation();
return () => flipInterval.current && clearInterval(flipInterval.current);
}, []);
const startAnimation = () => {
flipInterval.current = setInterval(() => {
setIsFlipping((prevFlipping) => !prevFlipping);
}, 10000);
};
setInterval
返回一个 ID,当我们离开组件时,我们将其传递给以结束订阅。 为 null 或此 id。如果我们不在这里使用,每次我们从 null 切换到 id 或从 id 切换到 null 时,这个组件都会重新渲染,这将创建一个无限循环。clearInterval
flipInterval.current
ref
- 如果不需要更新 UI,请使用 用于存储状态变量。
useRef
假设在 react native 应用程序中,我们为某些对 UI 没有影响的操作设置声音。对于一个状态变量,它可能不会节省那么多性能,但是如果您玩游戏并且需要根据游戏状态设置不同的声音。
const popSoundRef = useRef<Audio.Sound | null>(null);
const pop2SoundRef = useRef<Audio.Sound | null>(null);
const winSoundRef = useRef<Audio.Sound | null>(null);
const lossSoundRef = useRef<Audio.Sound | null>(null);
const drawSoundRef = useRef<Audio.Sound | null>(null);
如果我使用 ,每次更改状态值时,我都会继续重新渲染。useState
假设您需要为组件使用 ID。如果你用它创建它,它会随着每次重新渲染而改变。
useState
常量 [id,setId]=useState(uuid.v4())
如果您希望 id 在每次重新渲染时都不更改
const id = useRef(uuid.v4());
useRef() 只更新值,而不是重新渲染你的 UI,如果你想重新渲染 UI,那么你必须使用 useState() 而不是 useRe。如果需要更正,请告诉我。
useState 和 useRef 之间的主要区别是 -
引用的值在组件重新渲染之间保持不变(保持不变),
更新引用不会触发组件重新渲染。 但是,更新状态 c会重新渲染组件
useRef
引用更新是同步的,更新的引用值立即可用,但状态更新是异步的 - 该值在重新呈现后更新。
要查看使用代码:
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>;
}
每次单击该按钮时,它都会显示我已渲染!
但是,随着useRef
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>;
}
我被渲染将只被控制台记录一次。
如前所述,在许多不同的地方,更新会触发组件的渲染,而更新则不会。useState
useRef
在大多数情况下,有一些指导原则会有所帮助:
为useState
- 与 / 一起使用的任何内容都应该有一个状态,该状态会随着您设置的值而更新。
input
TextInput
- 当您需要触发器来重新计算 中的值或触发效果时
useMemo
useEffect
- 当您需要呈现所使用的数据时,该呈现仅在对事件处理程序或其他事件处理程序执行操作后才可用。例如 需要提供的数据。
async
useEffect
FlatList
为useRef
- 使用它们来存储用户不可见的数据,例如事件订阅者。
- 对于上下文或自定义钩子,请使用它来传递由 / 更新或由 / 触发的道具。我倾向于犯的错误是将类似的东西放置为状态,然后当我更新时,当该状态实际上是链的最终结果时,它会触发整个重新渲染。
useMemo
useEffect
useState
useReducer
authState
- 当您需要通过
ref
简单地说,如果你只需要读取一个值而从不更新该值,那么请使用 Refs
评论