React+Typescript:修复UseEffect Hook(回调+清理函数错误)

React+Typescript: Fixing UseEffect Hook ( Callback + clean up function errors)

我正在做一项让我抓狂的学校作业。这是一个小游戏,用户可以点击两张仓鼠图片中的一张来选出最可爱的那只。

为此我不得不使用 React + Typescript。

游戏现在运行得很好,但我在控制台收到两条消息,我必须在发送作业之前修复这些消息,但我不明白如何解决,尤其是因为我觉得 typescript 正在这样做更难了。

一个是:'sendRequest' 函数使 useEffect Hook 的依赖项(第 23 行)在每次渲染时都会发生变化。将它移到 useEffect 回调中。或者,将 'sendRequest' 的定义包装在它自己的 useCallback() Hook react-hooks/exhaustive-deps

另一个是:警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要修复,请在 useEffect 清理函数中取消所有订阅和异步任务。

第二个只有在我开始新游戏后才会出现。

这两个错误都发生在我的“fighterCardOverlay”组件中。这是哪个:

import { Hamster } from '../../models/Hamster';
import { useEffect, useState } from 'react';

interface CardGridProps {
    fighter: string;
    showInfo: boolean;
}

type Hamsters = Hamster;

const FighterCardOverlay = ({ fighter, showInfo }: CardGridProps) => {

    const [updatedData, setUpdatedData] = useState<Hamsters | null>(null);

    async function sendRequest(saveUpdatedData: any) {
    const response = await fetch('/hamsters/'+ fighter);
    const data = await response.json()
    saveUpdatedData(data)
    }

    useEffect(() => {
        sendRequest(setUpdatedData)
    }, [showInfo, sendRequest])


    
    return (
        <div className="fighterCardOverlay" >
           {updatedData ?
           <div>
            <li>Name: {updatedData.name}</li>
            <li>Age: {updatedData.age}</li>
            <li>Loves: {updatedData.loves}</li>
            <li>Favorite Food: {updatedData.favFood}</li>
            <li>Games: {updatedData.games}</li>
            <li>Wins: {updatedData.wins}</li>
            <li>Defeats: {updatedData.defeats}</li>
            </div>
            : 'Loading info...'
            }

        </div>
    )
}




export default FighterCardOverlay

作业要求在用户选择获胜者后,弹出关于用户可以阅读的每只仓鼠的更多信息,因此总体而言。为了获得更新的游戏数量和每只仓鼠 wins/defeats 的正确信息,我必须在用户选择获胜者后再次从服务器获取数据。为此,我在 UseEffect 上添加了这应该 运行 每当切换叠加层的状态发生变化时 (showInfo),并且它作为道具从渲染叠加层的“游戏”组件发送。

代码如下:

import { useEffect, useState } from 'react';
import { Hamster } from '../../models/Hamster';
import FighterCard from './FighterCard'
import '../../styles/game.css'
import FighterCardOverlay from './FighterCardOverlay';

type Hamsters = Hamster;

const Game = () => {

    const [fighters, setFighters] = useState<Hamsters[] | null >(null);
    const [showInfo, setShowInfo] = useState(false);
   
    

    useEffect(() => {
        getFighters(setFighters)
    }, [])


    
    function handleShowInfo(){
            
            if(showInfo === true ){
                setShowInfo(false)
            } else if(showInfo === false) {
                setShowInfo(true)
            }
        }

    function newGame() {
        getFighters(setFighters)
        handleShowInfo()
    }


    function updateWin(id: string, wins: number, games: number) {
        const newWins = wins + 1;
        const newGames = games + 1;

        return fetch('/hamsters/' + id, {
        method: 'PUT',
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({wins: newWins, games: newGames})
        })
        
    }

    function updateDefeats(id:string, defeats: number, games: number) {
        const newDefeats = defeats + 1;
        const newGames = games + 1;

        return fetch('/hamsters/' + id, {
        method: 'PUT',
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({defeats: newDefeats, games: newGames})
        })

        
    }

    function updateWinnersAndLosers(winnerId: string) {

         if( fighters ) { 

            const winner = fighters.find(fighter => fighter.id === winnerId);
            const loser = fighters.find(fighter => fighter.id !== winnerId);
            if(winner){
              updateWin(winner.id, winner.wins, winner.games); 
            }
            if(loser){
                updateDefeats(loser.id, loser.defeats, loser.games);
            };

            handleShowInfo();
            
        } else {
            console.log('fighters is undefined')
        }
        
     }

    return (
        <div className='game'>
            <h2>Let the fight begin!</h2>
            <p>Click on the cutest hamster</p>

            <section className="fighters">
                {fighters
                ? fighters.map(fighter => (
                    <div className="fighterCardContainer" key={fighter.id}>
                        <FighterCard fighter={fighter} updateWinnersAndLosers={updateWinnersAndLosers} />
                        {showInfo && <FighterCardOverlay fighter={fighter.id} showInfo={showInfo}/>}
                    </div>
                ))
                : 'Loading fighters...'}
            </section>

            <button className="gameButton" onClick={newGame}>New Game</button>

        </div>
    )
};

    

async function getFighters(saveFighters: any) {
    try {
    const response = await fetch('/hamsters');
    const data = await response.json()
    const fighters = await data.sort(() => .5 - Math.random()).slice(0,2)
    saveFighters(fighters)
    } catch (error) {
        saveFighters(null)
     }
};

export default Game 

正如我所说,游戏运行良好,我获得了正确的信息并且没有崩溃。但是,如果您能帮助修复控制台中的错误,我们将不胜感激!

可以这样解决:

  useEffect(() => {
    const controller = new AbortController(); // It solves Warning: Can't perform a React state update on an unmounted component.
    const signal = controller.signal;

    (async () => { // you also can move it to useCallback (don't forget pass signal
      try {
        const response = await fetch('/hamsters/' + fighter, { signal });
        const data = await response.json();

        setUpdatedData(data);
      } catch (error) {
        // TODO: handle error
      }
    })();

    return () => {
      controller.abort();
    };
  }, [showInfo, fighter]);