单一 useEffect 中的 React 钩子状态

React hooks state in single useEffect

我正在尝试使用新的 React hooks 功能,但遇到了一些问题。

Fiddle

我有一个 useEffect,它调用 setInterval,更新本地状态。像这样:

  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    const k = setInterval(() => {
        setCounter(counter + 1);
    }, 1000);
    return () => clearInterval(k);
  }, []);

  return (
    <div>Counter via state: {counter}<br/></div>
  );

它不能正常工作,因为计数器在第一次调用时被捕获,因此计数器停留在 1 值。

如果我使用 refs,ref 会更新,但不会调用重新渲染(在 UI 中只会看到 0 值):

  const counterRef = React.useRef(0);

  React.useEffect(() => {
    const k = setInterval(() => {
      counterRef.current += 1;
    }, 1000);
    return () => clearInterval(k);
  }, []);

  return (
    <div>Counter via ref: {counterRef.current}</div>
  );

我可以通过组合它们来制作我想要的东西,但它看起来确实不对:

  const [counter, setCounter] = React.useState(0);
  const counterRef = React.useRef(0);

  React.useEffect(() => {
    const k = setInterval(() => {
        setCounter(counterRef.current + 1);
      counterRef.current += 1;
    }, 1000);
    return () => clearInterval(k);
  }, []);

  return (
    <div>Counter via both: {counter}</div>
  );

请问谁能用hooks妥善处理这种情况?

useRef 仅在不需要组件更新的情况下才有用。但是在异步 useEffect 中访问当前状态的问题可以用相同的方法解决,即使用对象引用而不是不可变状态:

  const [state, setState] = React.useState({ counter: 0 });

  React.useEffect(() => {
    const k = setInterval(() => {
        state.counter++;
        setCounter(state);
    }, 1000);
    return () => clearInterval(k);
  }, []);

React 社区不鼓励使用可变状态,因为它比不可变状态有更多缺点。

与 class 组件中的 setState 一样,状态更新函数可用于在状态更新期间获取当前状态:

  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    const k = setInterval(() => {
        setCounter(conter => counter + 1);
    }, 1000);
    return () => clearInterval(k);
  }, []);

或者,setTimeout 可用于每秒设置新的间隔:

  React.useEffect(() => {
    const k = setTimeout(() => {
        setCounter(counter + 1);
    }, 1000);
    return () => clearInterval(k);
  }, [counter]);

我已经遇到过几次这种情况,我的首选解决方案是使用 useReducer 而不是 useState,如下所示:

  const [counter, dispatch] = React.useReducer((state = 0, action) => {
    // better declared outside of the component
    if (action.type === 'add') return state + 1
    return state
  });

  React.useEffect(() => {
    const k = setInterval(() => {
        dispatch({ type: 'add' });
    }, 1000);
    return () => clearInterval(k);
  }, []);

  return (
    <div>Counter via state: {counter}<br/></div>
  );

虽然添加了一些样板,但它确实简化了 "when is my variable taken in account ?"。 更多内容 https://reactjs.org/docs/hooks-reference.html#usereducer