【问题标题】:React: useState or useRef?反应:useState 还是 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);
    };
  });

  // ...
}

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

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案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”。

            【讨论】:

              猜你喜欢
              • 2020-06-20
              • 2021-04-01
              • 2021-01-05
              • 1970-01-01
              • 2021-08-28
              • 2021-06-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多