javascript - React State 未更新关键事件

标签 javascript reactjs typescript

我遇到了一个我无法弄清楚的问题。我正在构建一个 Wordle 克隆,状态似乎在某些事件上更新,而在其他事件上没有更新,我不太明白为什么。

我有一个 Keyboard 组件,它将 handleKeyClick 作为父组件的 prop,并附加到两个事件处理程序。

父组件

import { Box, Divider, Grid, Typography } from "@mui/material";
import { useState, useEffect, useCallback } from 'react';
import Keyboard from "../Keyboard";
import { v4 as uuid } from 'uuid'
import WordleNotifbar from "../WordleNotifBar";
import Loading from "../Utils/Loading";
import { IGuessState } from "../../types";

interface IGuessGridProps {
    addGuess: Function,
    guesses: any,
    answer: any
}
const GuessGrid = (props: IGuessGridProps) => {

    const { addGuess, guesses, answer } = props;
    let [notif, setNotif] = useState<boolean>(false);

    const [guess, setGuess] = useState<string[]>([]);
    const styles = {
        input: {
            border: ".5px solid white",
            height: "50px",
            display: "flex",
            borderRadius: "5px",
            justifyContent: "center",
            alignItems: "center",
            backgroundColor: "",
            color: "white",
        },
        container: {
            minWidth: "300px",
            width: "30%",
            maxWidth: "450px",
            margin: "0 auto",
            marginTop: "15px",
        },
    }

// In the parent component, I have defined the function I'm passing in as a prop as such: 
    const handleAddCharacter = (char: string) => {
        setGuess([...guess, char])
    }

// Not fully implemented yet 
    const handleBackspace = (e: MouseEvent): void => {
        e.preventDefault();
        setGuess([...guess])
    }

    const handleSubmit = (): void => {
        let word = guess.join('')
        if (word.length === answer.length) {
            addGuess(word.toLowerCase())
            setGuess([]);
        }
        else {
            setNotif(true);
            setTimeout(() => {
                setNotif(false);
            }, 1000)
        }
    }
    if (answer) {
        return <>
            <Divider />
            <Grid container sx={styles.container} >
                {answer.split('').map((_: string, index: number) => {
                    return (<Grid item xs={12 / answer.length} sx={styles.input} key={uuid()}>
                        <Box>
                            <Typography>
                                {guess[index]}
                            </Typography>
                        </Box>
                    </Grid>)
                })}
            </Grid>
            <Keyboard guesses={guesses} answer={answer} handleKeyClick={handleAddCharacter} handleBackspace={handleBackspace} submitFunc={handleSubmit} />
            {notif ? <WordleNotifbar message="Not Enough Characters" duration={1000} /> : ""}
        </>;
    } else {
        return <Loading />
    }
};

export default GuessGrid;

键盘组件

import { Box, Grid, SxProps, Theme, Typography } from "@mui/material";
import { useCallback, useEffect, useState } from 'react';
import { v4 as uuid } from 'uuid';
import BackspaceIcon from '@mui/icons-material/Backspace';
import React from "react";

interface IKeyboardProps {
    guesses: string[],
    answer: string,
    handleKeyClick: any,
    submitFunc: any,
    handleBackspace: any
}
const Keyboard = (props: IKeyboardProps) => {
    const { guesses, answer, handleKeyClick, submitFunc, handleBackspace } = props
    const [guessedLetters, setGuessedLetters] = useState<string[]>();
    const topRow = 'qwertyuiop'.toUpperCase().split('');
    const middleRow = 'asdfghjkl'.toUpperCase().split('');
    const bottomRow = 'zxcvbnm'.toUpperCase().split('');
    const allKeys = topRow.concat(middleRow.concat(bottomRow));

// When the component is initialized, I am establishing an event listener in the window for the key press events.

    useEffect(() => {
        window.addEventListener('keypress', handlePhysicalKeyPress)
    }, [])

    useEffect(() => {
        const allGuessedCharacters = guesses.join('').split('');
        const uniqueGuessedCharacters = allGuessedCharacters.filter((val: string, index: number, self) => self.indexOf(val) === index)
        setGuessedLetters(uniqueGuessedCharacters);
    }, [guesses])

    const handleVirtualKeyPress = (e: any) => {
        handleKeyClick(e.target.textContent)
    }

    const handlePhysicalKeyPress = (e: KeyboardEvent) => {
        e.preventDefault()
        if (allKeys.includes(e.key.toUpperCase())) {
            handleKeyClick(e.key.toUpperCase());
        }
    }

    const genKeyStyles = (character: string, _: number): SxProps<Theme> => {
        character = character.toLowerCase()
        const styles = {
            width: character === "bs" || character === "enter" ? "63px" : "33px",
            marginX: "1px",
            marginY: "1px",
            borderRadius: "5px",
            height: "50px",
            color: "black",
            textAlign: "center",
            backgroundColor: "#DDD",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
        };
        if (guessedLetters) {
            if (answer.indexOf(character) >= 0 && guessedLetters.indexOf(character) >= 0) {
                styles.backgroundColor = "green"
            } else if (answer.indexOf(character) < 0 && guessedLetters.indexOf(character) >= 0) {
                styles.backgroundColor = "#777"
            }
        }
        return styles
    }

    return <Box sx={{ display: "flex", flexDirection: "column", justifyContent: "center", marginTop: "10px", }}>
        <Box sx={{ display: "flex", justifyContent: "center" }}>
            {topRow.map((letter: string, index: any) => {
                return (
                    <Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
                        <Typography key={uuid()}>{letter}</Typography>
                    </Box>
                )
            })}
        </Box>
        <Box sx={{ display: "flex", justifyContent: "center" }}>
            {middleRow.map((letter: string, index: any) => {
                return (
                    <Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
                        <Typography key={uuid()}>{letter}</Typography>
                    </Box>
                )
            })}
        </Box>
        <Box sx={{ display: "flex", justifyContent: "center" }}>
            <Box sx={genKeyStyles("enter", 1)} key={uuid()} onClick={submitFunc}>
                <Typography key={uuid()}>enter</Typography>
            </Box>

            {bottomRow.map((letter: string, index: any) => {
                return (
                    <Box sx={genKeyStyles(letter, index)} key={uuid()} onClick={handleVirtualKeyPress}>
                        <Typography key={uuid()}>{letter}</Typography>
                    </Box>
                )
            })}
            <Box sx={genKeyStyles("bs", 1)} key={uuid()} onClick={handleBackspace}>
                <Typography key={uuid()}><BackspaceIcon /></Typography>
            </Box>
        </Box>
    </Box>
};

export default Keyboard;

发生的情况是,虚拟按键似乎正确更新了状态,但物理按键似乎将状态重置回空数组。我真的无法找出发生这种情况的充分理由。有什么想法吗?我提前感谢您的帮助!

Link to Live Application

最佳答案

当你这样做时:

    useEffect(() => {
        window.addEventListener('keypress', handlePhysicalKeyPress)
    }, [])

...您正在附加一个特定 handlePhysicalKeyPress 函数作为事件监听器。但是该函数是在每个组件重新渲染时重新创建的,因此您不再引用“当前”函数“版本”(如果您尝试删除它,您将无法删除它,因为它不再是相同的引用) .

因此,实际的监听器是函数的第一个“版本”,它调用 handleKeyClick 属性的第一个“版本”,这是您的函数的第一个“版本” handleAddCharacter 函数,它只知道您的guess 状态的第一个版本...这是一个空数组。

这就是为什么当通过按键执行handlePhysicalKeyPress时,它会从空数组构建一个新的猜测数组。

虽然您应该避免附加到事件监听器的内容与实际的“渲染时间”函数之间的这种差异,但针对您的具体情况应该有一个非常简单的解决方案:您应该使用 functional form你的状态 setter ,即使它是“第一个版本”,它也应该使用“当前”状态版本:

setGuess((currentGuess) => [...currentGuess, char])

关于javascript - React State 未更新关键事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71010357/

相关文章:

php - BibTeX格式查询

css - 如何为 slider 添加 CSS

javascript - 在不使用 React 调整标题大小的情况下从标题创建下拉列表

javascript - 如何通过 clickHandler 进行导航?

inheritance - 如何在 TypeScript 的派生类中重载构造函数?

javascript - 每次生产 Kafka 消息时都需要连接吗?

javascript - 如何在 Selenium 中执行 JavaScript 并获取数据?

javascript - 检查动态网页是否已完全加载的最佳方法是什么?

javascript - 按值和顺序确定数组

Angular 2 : how to resolve Error: (SystemJS) module is not defined?