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
定义,因为它只包含 useState
的 setState
个。
但不需要定义 setWinner
包装器。这是您提高代码可读性的选择。
解决方案 #2
但我认为如果你需要在选择获胜者时播放声音,只需在选择获胜者时调用playSound
即可。
// when winner is selected
setWinner(winner);
if(soundIsOn){
playSound(winSound);
}
简而言之:
我有一个副作用,我想在值更改时发生,但前提是第二个值是 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
定义,因为它只包含 useState
的 setState
个。
但不需要定义 setWinner
包装器。这是您提高代码可读性的选择。
解决方案 #2
但我认为如果你需要在选择获胜者时播放声音,只需在选择获胜者时调用playSound
即可。
// when winner is selected
setWinner(winner);
if(soundIsOn){
playSound(winSound);
}