我想为一个可以重置的游戏创建一个计时器。如果我将计时器的状态提升到父级,游戏就会变得无响应

问题描述 投票:0回答:1

我目前正在构建一个计时器组件,用于计算从网站加载那一刻起玩家完成游戏所需的时间。如果我将计时器的状态提升到父组件,整个应用程序每秒都会重新渲染,从而使我的游戏无响应。不过,我希望能够从我的父组件重置计时器。

我已将状态移动到它自己的组件,这意味着我的应用程序不会不断重新渲染,但是我正在努力允许我的父组件重置我的子计时器组件中的计时器。

这是我的计时器组件 - 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>

这可以重置计时器,但是每当我单击它时,它都会在控制台中弹出许多关于重新渲染次数过多的错误。此外,如果我在一秒的时间间隔内单击重置计时器按钮,它会说我无法在子组件重新渲染时重新渲染父组件,从而使我的按钮偶尔无响应。

javascript reactjs react-hooks
1个回答
0
投票

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>;
});
© www.soinside.com 2019 - 2024. All rights reserved.