react刽子手游戏:单击状态未正确管理产生意外行为

IT技术 reactjs
2022-07-29 00:31:02

在此处输入图像描述

您好,我有 Letters.js,它为 az 生成 AvailableLetter。

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

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

    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 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} />
            );
        }
    );



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

export default Letters;

我在这里有 AvailableLetter.js,我希望它在第一次点击后无法点击。

import React, {useState, useEffect} from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';

const AvailableLetter = (props) => {
    // const [show,setShow]=useState(true);
    // const [clicked, setClicked]=useState(false);
    // const [outcome,setOutcome]=useState(false);
    const [clicked,setClicked]=useState(false);

    // if (show)
    // {
    //     setClicked(true);
    // }

    // const play = (alphabet) => {
    //     const solution = props.solution.split('');
    //     if (solution.indexOf(alphabet)<0)
    //     {
    //         return false;
    //     }
    //     else
    //     {
    //         return true;
    //     }
    // }

    const setStuff = () => {
        // setShow(true);
        setClicked(false);
        props.setSolved();
    };
    useEffect( setStuff,[clicked] );

    // useEffect( ()=>setShow(true),[show] );
    // useEffect( ()=>props.setSolved(),[show] );

    if (clicked)         // STRANGELY THIS PART WORKS!!!
    {
        if (props.play())
        {
            props.correct();
            // alert('correct');
        }
        else
        {
            props.incorrect();
            // alert('wrong');
        }
    }

    const attachedClasses = [classes.AvailableLetter];
    const disableLetter = () => {
        attachedClasses.push(classes.Disabled);
        setClicked(true);
    };

    // const letter = <span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>;

    let letter=null;
    if (!clicked)
    {
        letter = <span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>;
    }
    else if(clicked)    // CODE NEVER GETS HERE!!!!!!!!!!!!!!
    {
        letter = <span className={attachedClasses.join(' ')} >{props.alphabet}</span>;
    }

    return (
        <Ax>
            {letter}
        </Ax>
    );
}

export default AvailableLetter;

它的 CSS 文件是 AvailableLetter.module.css:

.AvailableLetter
{
    border: 1px solid black;
    padding: 10px;
    margin: 3px;
}

.AvailableLetter.Disabled
{
    pointer-events: none;
    background: #aaa;
}

看来我在 AvailableLetter 中的逻辑是正确的,但它永远不会到达 else if (clicked) 部分,并且字母始终保持可点击状态。

AvailableLetter.js 内部:如果我改用按钮:

<button disable={clicked} onClick={()=>setClicked(true)}>props.alphabet</button>

即使在 setClicked(true) 时,奇怪的禁用也不起作用。

但如果我这样做

<button disable>props.alphabet</button>

现在它禁用了。

我感谢您的帮助!

更新:从 setStuff() 中删除 setClicked(false) 会出错:

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
1个回答

你的效果和钩子的组合创造了一个反馈循环。

这个按钮:

<span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>

调用这个函数:

    const disableLetter = () => {
        attachedClasses.push(classes.Disabled);
        setClicked(true);
    };

设置clickedtrue. 一旦发生这种情况,这个效果就会运行:

    const setStuff = () => {
        // setShow(true);
        setClicked(false);
        props.setSolved();
    };
    useEffect( setStuff,[clicked] );

这立即clicked == false再次使。还值得注意的是,setStuff第二次调用是因为clicked更改了值,再次触发了效果。在这种情况下setStuff应该做什么?删除对该setClicked(false)函数的调用,并clicked应保持为true.

我强烈建议清理您的代码,以便更容易理解。像这样的逻辑:

if (!clicked) {
  // not clicked
} else if (clicked) {
  // clicked 
}

可以很容易地这样描述:

if (clicked) {
  // clicked
} else {
  // not clicked
}

通过这样做,您在调试遇到的问题时会省去很多麻烦。

最大更新深度误差

根据您在问题/粘贴箱中的堆栈跟踪和代码,您有另一个由这部分引起的循环:

    if (clicked)
    {
        if (props.play())
        {
            props.correct();
            // alert('correct');
        }
        else
        {
            props.incorrect(); // <- this is your trigger
            // alert('wrong');
        }
    }

您应该将此代码移动到您的setStuff函数中,以便效果仅调用一次。

我还建议在这里重新考虑您的结构,以便您(和其他人)可以遵循您正在做的更好的事情。堆栈跟踪将帮助您解决您遇到的任何进一步错误,以便您可以跟踪您可能遇到的更多循环的来源。