首页 > 解决方案 > 意外的 useEffect 行为

问题描述

我正在制作刽子手游戏。当字母被点击时,它不应该再被点击了。

这是关于代码沙盒的简化版本: https ://codesandbox.io/s/intelligent-cori-6b80q

在沙箱中,当单击字母时,它会变为“CLICKED!”。这里的一切似乎都很好。

但在我的项目中,我得到了奇怪的行为。

可用Letter.js

const AvailableLetter = (props) => {

    const [clicked,setClicked]=useState(false);

    const setStuff = () => {
        setClicked(false); // If I delete this I get Error: Maximum update depth exceeded.
        props.setSolved();
    };
    useEffect( setStuff,[clicked] );

    if (clicked)         // IF CLICKED REMAINS TRUE, EVERY LETTER GETS PLAYED.
    {
        if (props.play())
        {
            props.correct();
        }
        else
        {
            props.incorrect();
        }
    }

    const disableLetter = () => {
        setClicked(true);
    };

    let letter=span onClick={disableLetter}>{props.alphabet}</span>;

    if(clicked)    // CODE NEVER GETS HERE!!!!!!!!!!!!!!
    {
        letter = <span>{props.alphabet}</span>;
    }

    return (
        <Ax>                 // Ax is a high order class, just a wrapper
            {letter}
        </Ax>
    );
}

即使单击后,字母仍可单击。这不是我想要的。

每个字母都由 Letters.js 呈现,它在 az 中提供并生成自定义的 AvailableLetter。

const availableLetters = [ ...allLetters ].map(
        (alphabet,i) => {
            return (
                <AvailableLetter setSolved={props.setSolved} play={()=>playHandler(alphabet)} correct={()=>props.correct(alphabet)} incorrect={()=>props.incorrect(alphabet)} solution={props.solution} key={i} alphabet={alphabet} />
            );
        }
    );

所以这里要解决的问题是:

- Letters remain clickable even after a click
- If setClicked(false) is removed it causes an infinite loop
const setStuff = () => {
        setClicked(false);    // if removed causes infinite loop
        props.setSolved();
    };

所有这一切都很奇怪,因为在代码和框中我不需要在 setEffect() 中将 clicked 设置为 false。

你可以在这里看到所有的代码:https ://github.com/guntinug/hangmanerrors/tree/master/src 请看一下项目代码,它不长。

标签: reactjs

解决方案


这是一个结构性问题,我不得不将处理函数和状态委托给父组件 Letters.js

我必须让它如丝般顺滑:p

import React, {useState} from 'react';
import AvailableLetter from './AvailableLetter/AvailableLetter';
import DisabledLetter from './DisabledLetter/DisabledLetter';
import classes from './Letters.module.css';

const Letters = (props) => {
    const [lettersMap, setLettersMap]=useState(
        {
            "a":false,"b":false,"c":false,"d":false,"e":false,"f":false,"g":false,"h":false,"i":false,"j":false,"k":false,"l":false,"m":false,"n":false,"o":false,"p":false,"q":false,"r":false,"s":false,"t":false,"u":false,"v":false,"w":false,"x":false,"y":false,"z":false
        }
    );

    const updateClickedHandler = (letter) => {
        setLettersMap(
            {
                ...lettersMap,[letter]:true
            }
        );
    };

    const playHandler = (alphabet) => {
        const solution = props.solution.split('');
        console.log(solution);

        if (solution.indexOf(alphabet)<0)
        {
            console.log('incorrect');
            return false;
        }
        else
        {
            console.log('correct');
            return true;
        }
    }

    const renderedLetters = Object.keys(lettersMap).map(
        (letter,i)=>{
          if (!lettersMap[letter])     //letter is not yet clicked
          {
            return (
                <AvailableLetter updateClicked={updateClickedHandler} setSolved={props.setSolved} play={()=>playHandler(letter)} correct={()=>props.correct(letter)} incorrect={()=>props.incorrect(letter)} solution={props.solution} key={i} alphabet={letter} />
            )
          }
          else                         //letter is clicked
          {
            return (
                <DisabledLetter alphabet={letter} />
            )
          }
        }
    );



    return (    
        <div className={classes.Letters}>
            <p>Solution: {props.solution}</p>
            <div className={classes.AvailableLetters}>
                {renderedLetters}
            </div>
        </div>
    );
}

export default Letters;

import React from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';

const AvailableLetter = (props) => {

    const setStuff = () => {
      if (props.play()) {
        props.correct();
      }
      else {
        props.incorrect();
      }

      props.updateClicked(props.alphabet);
      props.setSolved();
    };

    return (
        <Ax>
          <span className={classes.AvailableLetter} onClick={setStuff}>{props.alphabet}</span>
        </Ax>
    );
}

export default AvailableLetter;

最后,我添加了另一个名为 DisabledLetter.js 的组件

import React from 'react';
import classes from './DisabledLetter.module.css';

const DisabledLetter = (props) => {
    return (
    <span className={classes.DisabledLetter} >{props.alphabet}</span>
    );
};

export default DisabledLetter;

您可以在此处查看整个源代码 https://github.com/guntinug/hangmanclicked/tree/master/src ;)

但是发现了一个问题,当游戏解决后,我需要再单击一个字母才能显示“你赢了”消息。

当我死时会立即显示“游戏结束”。


推荐阅读