反应:useState 还是 useRef?

Posted

技术标签:

【中文标题】反应:useState 还是 useRef?【英文标题】:React: useState or useRef? 【发布时间】:2019-10-20 16:37:12 【问题描述】:

我正在“Hooks FAQ”阅读有关 React useState()useRef() 的信息,我对一些似乎同时具有 useRef 和 useState 解决方案的用例感到困惑,但我不是确定哪种方式是正确的。

来自“Hooks 常见问题解答”about useRef():

“useRef() Hook 不仅仅用于 DOM refs。“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);
    ;
  );

  // ...

两个示例的结果相同,但哪个更好 - 为什么?

【问题讨论】:

【参考方案1】:

两者的主要区别是:

useState 会导致重新渲染,useRef 不会。

它们的共同点是,useStateuseRef 在重新渲染后都可以记住它们的数据。因此,如果您的变量决定了视图层渲染,请使用useState。否则使用useRef

我建议阅读此article。

【讨论】:

另一个很大的区别是设置状态是异步的,设置引用是同步的。 在实践中引入一个单独的(令人困惑的)钩子真的会产生如此大的不同吗? React 做很多事情都违背了最佳性能(注册和注册),但我们应该注意避免重新渲染吗?怎么会? 当您将 useRef 与原生 html 输入元素一起使用时,您的组件是不受控制的输入,而使用 useState 它是受控的【参考方案2】:

基本上,我们在这些情况下使用 UseState,在这些情况下,状态的值应该通过重新渲染来更新。

当您希望您的信息在组件的整个生命周期内保持不变时,您将使用 UseRef,因为它不适用于重新渲染。

【讨论】:

【参考方案3】:

如果你存储了区间 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]);

  // ...

【讨论】:

【参考方案4】:

useRef 在您想跟踪值更改但不想触发重新渲染或useEffect 时很有用。

大多数用例是当你有一个依赖于 value 的函数,但 value 需要由函数结果本身更新。

例如,假设您要对某些 ​​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,但不想通过其更改触发useCallbackuseEffect

我们可以通过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]);

我们可以在useRef 的帮助下从useCallback deps 数组中移除currentPage 依赖,这样我们的组件就免于死循环了。

【讨论】:

【参考方案5】:

你也可以使用useRef来引用一个dom元素(默认的HTML属性)

例如:分配一个按钮来关注输入字段。

useState 只更新值并重新渲染组件。

【讨论】:

【参考方案6】:

这实际上主要取决于您使用计时器的目的,这不清楚,因为您没有显示组件呈现的内容。

如果你想在组件的渲染中显示你的计时器的值,你需要使用useState。否则,您的 ref 值的变化不会导致重新渲染,并且计时器不会在屏幕上更新。

如果必须在计时器的每个滴答声更改 UI 视觉上发生其他事情,您可以使用 useState 并将计时器变量放入 a 的依赖项数组中useEffect 挂钩(您可以在其中执行 UI 更新所需的任何操作),或者根据计时器值在渲染方法(组件返回值)中执行您的逻辑。 SetState 调用将导致重新渲染,然后调用您的 useEffect 挂钩(取决于依赖项数组)。 有了 ref,就不会发生更新,也不会调用 useEffect。

如果您只想在内部使用计时器,您可以改用 useRef。每当必须发生导致重新渲染的事情时(即经过一定时间后),您就可以在 setInterval 回调中使用 setState 调用另一个状态变量。这将导致组件重新渲染。

仅在真正需要时(即,在流量或性能问题的情况下)使用本地状态的引用,因为它不遵循“the React way”。

【讨论】:

以上是关于反应:useState 还是 useRef?的主要内容,如果未能解决你的问题,请参考以下文章

React useState 由于 useRef 而没有更新

反应 useState 总是给出以前的状态值

重载与 useRef 不匹配

如何更改我的非输入标签的反应?

useRef 反应钩子

为子组件(useRef)反应滑轮事件?