反应:useState或useRef?

时间:2019-06-05 07:23:54

标签: reactjs react-hooks

我在“ 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);
    };
  });

  // ...
}

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

4 个答案:

答案 0 :(得分:5)

两者之间的主要区别是:

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

它们之间的共同点是,useStateuseRef都可以在重新渲染后记住它们的数据。因此,如果您的变量是决定视图图层渲染的变量,请使用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,但不想通过其更改触发 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]);

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

  // ...
}