我目前正在构建一个计时器组件,用于计算从网站加载那一刻起玩家完成游戏所需的时间。如果我将计时器的状态提升到父组件,整个应用程序每秒都会重新渲染,从而使我的游戏无响应。不过,我希望能够从我的父组件重置计时器。
我已将状态移动到它自己的组件,这意味着我的应用程序不会不断重新渲染,但是我正在努力允许我的父组件重置我的子计时器组件中的计时器。
这是我的计时器组件 - resetTime 和 setResetTime 是父组件中的状态
import { useEffect, useState } from "react";
export default function Timer({
characters,
guessed,
resetTime,
setResetTime,
}) {
const initialTime = 0;
const [runningTime, setRunningTime] = useState(initialTime);
if (resetTime === true) {
setRunningTime(initialTime);
setResetTime(false);
}
useEffect(() => {
const key = setInterval(() => {
if (guessed.length < characters.length) {
setRunningTime((runningTime) => runningTime + 1);
} else {
clearInterval(key);
}
}, 1000);
return () => {
clearInterval(key);
};
}, [characters.length, guessed.length]);
return <h4>{`${runningTime}`}</h4>;
}
在我的父组件中,我有一个使用 ResetGame() 函数 onClick 的按钮
function resetGame() {
handleClose();
setGuess(initialGuess);
setGuessed(initialGuessed);
setResetTime(true);
}
<button onClick={resetGame}>Reset Game</button>
这可以重置计时器,但是每当我单击它时,它都会在控制台中弹出许多关于重新渲染次数过多的错误。此外,如果我在一秒的时间间隔内单击重置计时器按钮,它会说我无法在子组件重新渲染时重新渲染父组件,从而使我的按钮偶尔无响应。
Timer
实现的问题在于,它将状态更新作为无意的副作用直接排入函数组件主体中。该逻辑应该移入并应用到 useEffect
钩子中。
此外,当间隔开始时,
setInterval
回调会在当前characters.length
和guessed.length
值上关闭,因此内部检查毫无意义,因为这些值在间隔回调的生命周期内永远不会改变。可以删除。
我建议的另一个更改是将父组件中的
resetTime
状态切换回 false,这样就不需要传递它,因为它是一个实现细节。
示例:
function resetGame() {
handleClose();
setGuess(initialGuess);
setGuessed(initialGuessed);
setResetTime(true);
// place callback at end of event queue to allow React to process
// the above state updates and rerender
setTimeout(setResetTime, 0, false);
}
...
<Timer characters={characters} guessed={guessed} resetTime={resetTime} />
const initialTime = 0;
function Timer({ characters, guessed, resetTime }) {
const [runningTime, setRunningTime] = useState(initialTime);
useEffect(() => {
if (resetTime) {
setRunningTime(initialTime);
}
}, [resetTime]);
useEffect(() => {
const key = setInterval(() => {
setRunningTime((runningTime) => runningTime + 1);
}, 1000);
return () => {
clearInterval(key);
};
}, [characters.length, guessed.length]);
return <h4>{`${runningTime}`}</h4>;
}
我认为您对
useImperativeHandle
句柄有一个很好的用例,其中 Timer
组件定义了 resetTime
函数并通过 React 引用将其公开。这样,逻辑就耦合到使用它的组件,而不是依赖所有父组件来实现它。
示例:
const timerRef = useRef();
...
function resetGame() {
handleClose();
setGuess(initialGuess);
setGuessed(initialGuessed);
timerRef.current.resetTime();
}
...
<Timer ref={timerRef} characters={characters} guessed={guessed} />
import {
forwardRef,
useEffect,
useState,
useImperativeHandle,
} from "react";
const initialTime = 0;
const Timer = forwardRef(({ characters, guessed }, ref) => {
const [runningTime, setRunningTime] = useState(initialTime);
useImperativeHandle(
ref,
() => ({
resetTime: () => setRunningTime(initialTime),
}),
[]
);
useEffect(() => {
const key = setInterval(() => {
setRunningTime((runningTime) => runningTime + 1);
}, 1000);
return () => {
clearInterval(key);
};
}, [characters.length, guessed.length]);
return <h4>{`${runningTime}`}</h4>;
});