React useEffect 运行太频繁

React useEffect runs too often

简而言之:

我有一个副作用,我想在值更改时发生,但前提是第二个值是 true。由于两个值都必须在依赖数组中,因此当第一个值不变而第二个值变为'on'时,也会触发副作用'on',但我不希望这样。

详细:

我正在用 React 创建游戏。我想在各种事件中播放(短)音效,例如输赢。我有一个简单的辅助函数来播放声音,它有效:

function playSound(url) {
  new Audio(url).play();
}

所有游戏逻辑都在自定义挂钩中 useGame。这个钩子 returns,除此之外,还有一个用于用户移动时的点击处理程序(它处理更新钩子中的所有相关状态)和一个 winner 布尔值(指示游戏是否获胜).

我不能在点击处理程序中调用 playSound,因为那个闭包只能访问点击前的状态,不知道点击后游戏是否获胜。所以我把它放在一个副作用中,用一个依赖数组来确保它只在游戏获胜时发生一次。

useEffect(() => {
  if (winner) {
    playSound(winSound);
  }, [winner]);

[注意:在内部,winner是无状态的;它是直接从(有状态的)董事会计算的。所以没有 setWinner 个我可以标记的电话。]

到这里为止一切正常。但是现在我想添加一个用户设置来静音所有声音。

const [soundIsOn, setSoundIsOn] = useState(true);

现在我必须将其添加为调用 playSound 的条件(或将其作为参数传递给它)。

useEffect(() => {
  if (winner && soundIsOn) {
    playSound(winSound);
  }, [winner, soundIsOn]);

请注意,依赖项数组现在需要包含 soundIsOn声音关闭时出现问题,用户赢得游戏,然后用户稍后打开声音。由于 soundIsOn 更改,将调用此效果,从而产生获胜音效。

我通读了所有文档并查找了类似的问题。我见过的唯一可能的解决方案是 use a ref inside a custom hook 来跟踪以前的状态并检查哪个状态发生了变化,但这似乎有点混乱和不令人满意。有没有'proper'的方法来处理这种事情?理想情况下,要么通过修改效果,要么以某种方式将其应用到点击处理程序中。

解决方案 #1

使用其他状态而不是获胜者对象本身来管理触发事件。

const [winner, _setWinner] = useState(...);
const [shouldPlaySound, setShouldPlaySound] = useState(false);
// wrapper function (it is not required)
const setWinner = (winner) => {
   setShouldPlaySound(true);
   _setWinner(winner)
}
...
// if winner is selected
setWinner(...);
...
// Side effect
useEffect(() => {
   if(shouldPlaySound && soundIsOn){
       playSound(winSound);
   }
   setShouldPlaySound(false);
}, [shouldPlaySound, soundIsOn])

另外,新的 setWinner 包装函数不需要用 useCallback 定义,因为它只包含 useStatesetState 个。

但不需要定义 setWinner 包装器。这是您提高代码可读性的选择。

解决方案 #2

但我认为如果你需要在选择获胜者时播放声音,只需在选择获胜者时调用playSound即可。

// when winner is selected
setWinner(winner);
if(soundIsOn){
  playSound(winSound);
}