开发者问题收集

React hooks 函数有旧版本的状态变量

2019-03-08
6457

我尝试使用 Hooks 为自己制作一个漂亮的动画示例,但我偶然发现了一个问题,即我的函数没有状态变量的更新版本,并且继续使用第一个版本。

在下面的代码片段中,我有一个示例,一旦您单击一个条形图,它应该以方形形式移动。首先向东,然后向南,然后向西,然后向北,然后再次向东等。但是它永远不会向南,因为即使它的方向从 north 更新为 east (由条形图上的文本或再次单击它指示),函数仍然认为方向是北。

const Example = (props) => {
  const [ direction, setDirection ] = React.useState('North');
  console.log("Rendering Example: ", direction);
  
  const isAnimating = React.useRef(false)
  const blockRef = React.useRef(null);
  
  // Animate on click.
  const onClick = () => {
    if (!isAnimating.current) {
      decideDirection();
      isAnimating.current = true
    } else {
      console.log("Already animating. Going: ", direction);
    }
  };
  
  const decideDirection = () => {
    console.log("Current direction: ", direction);
    if (direction === 'North') {
      moveEast();
    } else if (direction === 'East') {
      moveSouth();
    } else if (direction === 'South') {
      moveWest();
    } else if (direction === 'West') {
      moveNorth();
    }
  };
  
  const move = (toX, toY, duration, onComplete) => {
    Velocity(blockRef.current, {
      translateX: toX,
      translateY: toY,
      complete: () => {
        onComplete();
      }
    },
    {
      duration: duration
    });
  }
  
  const moveNorth = () => {
    setDirection('North');
    console.log('Moving N: ', direction);
    move(0, 0, 500, () => {
      decideDirection();
    })
  }
  
  const moveEast = () => {
    setDirection('East');
    console.log('Moving E: ', direction);
    move(500, 0, 2500, () => {
      decideDirection();
    })
  };
  
  const moveSouth = () => {
    setDirection('South');
    console.log('Moving S: ', direction);
    move(500, 18, 500, () => {
      decideDirection();
    })
  }
  
  const moveWest = () => {
    setDirection('West');
    console.log('Moving W: ', direction);
    move(0, 18, 2500, () => {
      decideDirection();
    })
  }
  
  return(
    <div>
      <div id='block' onClick={onClick} ref={blockRef} style={{ width: '100px', height: '18px', backgroundColor: 'red', textAlign: 'center'}}>{direction}</div>
    </div>
  );
};

ReactDOM.render(<div><Example/></div>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>

我发现这有点奇怪,因为这些函数都没有记忆,所以它们应该重新创建每个渲染并因此具有新值。即使我添加了类似 useCallback 的内容并为每个函数提供 direction ,它仍然不起作用。为什么函数不知道状态变量的更新版本?

2个回答

问题是您的 move 函数已覆盖 decideDirection 的初始版本。您的 div 已重新渲染,但动画仍继续引用函数的初始版本。解决此问题的一种方法是使用 ref 指向 decideDirection 函数(我已在下面的代码片段中展示了这种方法)。

但这似乎有点脆弱,因为重新渲染的时间与动画的时间使得很难推断这是否是稳健的。更稳健的方法是涉及通过状态更改更明确地链接移动,这样您就不必在每个动画完成时直接调用 decideDirection ,而是设置将触发效果以开始下一个动画的状态。

const Example = (props) => {
  const [ direction, setDirection ] = React.useState('North');
  console.log("Rendering Example: ", direction);
  
  const isAnimating = React.useRef(false)
  const blockRef = React.useRef(null);
  const decideDirection = () => {
    console.log("Current direction: ", direction);
    if (direction === 'North') {
      moveEast();
    } else if (direction === 'East') {
      moveSouth();
    } else if (direction === 'South') {
      moveWest();
    } else if (direction === 'West') {
      moveNorth();
    }
  };
  const decideDirectionRef = React.useRef(decideDirection);
  React.useEffect(()=>{
    decideDirectionRef.current = decideDirection;
  });
  
  // Animate on click.
  const onClick = () => {
    if (!isAnimating.current) {
      decideDirectionRef.current();
      isAnimating.current = true
    } else {
      console.log("Already animating. Going: ", direction);
    }
  };
  
  const move = (toX, toY, duration, onComplete) => {
    Velocity(blockRef.current, {
      translateX: toX,
      translateY: toY,
      complete: () => {
        onComplete();
      }
    },
    {
      duration: duration
    });
  }
  
  const moveNorth = () => {
    setDirection('North');
    console.log('Moving N: ', direction);
    move(0, 0, 500, () => {
      decideDirectionRef.current();
    })
  }
  
  const moveEast = () => {
    setDirection('East');
    console.log('Moving E: ', direction);
    move(500, 0, 2500, () => {
      decideDirectionRef.current();
    })
  };
  
  const moveSouth = () => {
    setDirection('South');
    console.log('Moving S: ', direction);
    move(500, 18, 500, () => {
      decideDirectionRef.current();
    })
  }
  
  const moveWest = () => {
    setDirection('West');
    console.log('Moving W: ', direction);
    move(0, 18, 2500, () => {
      decideDirectionRef.current();
    })
  }
  
  return(
    <div>
      <div id='block' onClick={onClick} ref={blockRef} style={{ width: '100px', height: '18px', backgroundColor: 'red', textAlign: 'center'}}>{direction}</div>
    </div>
  );
};

ReactDOM.render(<div><Example/></div>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>
Ryan Cogswell
2019-03-08

您的值 已被捕获 ,请查看 我的另一个答案

Eric Yang
2021-02-23