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

Posted

技术标签:

【中文标题】React+Typescript:修复 UseEffect Hook(回调+清理函数错误)【英文标题】:React+Typescript: Fixing UseEffect Hook ( Callback + clean up function errors) 【发布时间】:2022-01-11 13:14:45 【问题描述】:

我正在完成一项让我发疯的学校作业。这是一个小游戏,用户点击两张仓鼠图片中的一张来挑选最可爱的获胜者。

为此我不得不使用 React + 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

任务要求在用户选择获胜者后,弹出更多关于用户可以阅读的每只仓鼠的信息,因此是overaly。为了获得更新的游戏数量和每只仓鼠获胜/失败的正确信息,我必须在用户选择获胜者后再次从服务器获取数据。为此,我在 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 

正如我所说,游戏运行良好,我得到了正确的信息并且它没有崩溃。但是,对于修复控制台中的错误的任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

可以这样解决:

  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]);

【讨论】:

非常感谢!我刚试了一下,控制台中的所有消息都消失了!但似乎出现了一个新问题。加载页面时,我没有获得第一个游戏的正确更新数据?不过,它确实适用于我之后开始的新游戏。我不明白这可能是什么原因。有什么想法吗? 我不确定我的代码是否会导致您出现新问题。以前,您的效果会运行每个渲染。但现在只有当 shoInfo 和 fighter 被改变时。更改依赖项全部删除它们,useEffect(() =&gt; /*effect*/) 删除依赖项成功了!谢谢! 如果我帮助你,你能标记我的答案吗?

以上是关于React+Typescript:修复 UseEffect Hook(回调+清理函数错误)的主要内容,如果未能解决你的问题,请参考以下文章

typescript eventTarget dataset 如何修复报错?

我想在 Laravel 项目中使用 TypeScript 和 React

如何为 TypeScript 定义文件导入文件

从提交处理函数内部更新 react/redux 状态

我的提交和编辑表单的 React 模态组件不会关闭?

useEffect 中的 state 总是使用 React Hooks 引用初始状态