You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React卡牌游戏开发:Props异步致子组件取值undefined问题求助

问题描述

在开发React卡牌游戏时遇到以下问题:

  • 父组件Opponents.jsx维护了opponents数组状态(格式如[name, name1]),通过props传递给子组件OpponentCards.jsx
  • 子组件的return渲染部分能正确读取props.opponent并显示对手名称,但在return之前的代码(比如console.log(props.opponent))中访问时,值为undefined
  • 已确认是props异步更新导致,但不清楚正确解决方式;另外在VSCode中刷新Vite但不刷新页面时,问题会暂时消失
相关代码

Opponents.jsx

import React, { useEffect, useState } from 'react'
import OpponentCards from './OpponentCards'

const Opponents = (props) => {

    const [opponents, setOpponents] = useState([])
    const [numberOfPlayers, setNumberOfPlayers] = useState('two')
    
    let updateUsers = (data) => {

        let usersToChange = data.users
        if(usersToChange.find(item => item === localStorage.getItem('name'))){
            while(String(usersToChange[0]) !== localStorage.getItem('name')){
                usersToChange.push(usersToChange[0])
                usersToChange.splice(0,1)
            }        

        }
        
        setOpponents(usersToChange)
        switch (props.users.length) {
            case 2 :
                setNumberOfPlayers('two') 
                return
            case 3 :
                setNumberOfPlayers('three') 
                return
            case 4 :
                setNumberOfPlayers('four') 
                return
        }
    } 

    props.socket.on('getUsersResponse', (data) => {
        updateUsers(data)
    })


    useEffect(() => updateUsers(props) ,[props.users])


    console.log(opponents) //correct array of opponents

    return (
        <div className={numberOfPlayers+'-players'}>
            <div className="opponent one">
                <OpponentCards opponent = {opponents[1]} number={1}  socket= {props.socket}/>
            </div>

            <div className="opponent two">
                <OpponentCards opponent = {opponents[2]} number={3}  socket= {props.socket}/>
            </div>

            <div className="opponent three">
                <OpponentCards opponent = {opponents[2]} number={3}  socket= {props.socket}/>
            </div>
        </div>
    )
}

export default Opponents

OpponentCards.jsx

import React, { useEffect, useState } from 'react'
import userLogo from '../../sourses/Untitled.svg'

const OpponentCards = (props) => {

    let [cards, setCards] = useState(0) 
    
    
    let addCard = (name) => {

        if(name === props.opponent){
            setCards(cards => cards +1)
        }
    }


    useEffect(() => {
        props.socket.on('getCardFromDeckToOpponentResponse',(name) => addCard(name))
    },[props.socket])

    console.log(props.opponent)//undefined

    return (
        <>
        <div className="container">
            <h3 className='name'>
                {props.opponent ? props.opponent : 'Player ' + (props.number + 1)}
            </h3>
            <img src={userLogo} />
            <div className="opponentCards">
                {[...Array(cards)].map((elem, index)=><div key={index} className='card'>x</div>)}
            </div>
        </div>

        </>
    )
}

export default OpponentCards
解决思路及方案

1. 根本原因

  • 组件初始化时,父组件opponents初始值为空数组,此时传递给子组件的opponents[1]、opponents[2]都是undefined,所以子组件第一次渲染时props.opponent为undefined
  • 父组件通过socket或useEffect更新opponents状态后,会触发重新渲染并传递更新后的props,但子组件中console.log只在初始渲染时执行一次,因此看不到更新后的值;而return部分会在每次渲染时更新,所以能显示正确名称
  • Vite热刷新时会重置组件状态,可能刚好让opponents在初始化时就有值,导致问题暂时消失

2. 具体修复方案

方案一:监听props变化,确保逻辑在props更新后执行

在子组件OpponentCards.jsx中添加useEffect监听props.opponent的变化,确保在props更新时执行相关逻辑:

useEffect(() => {
  // 当props.opponent更新时,执行需要的逻辑
  console.log('更新后的opponent:', props.opponent);
}, [props.opponent]); // 依赖项设为props.opponent,变化时触发

方案二:修复socket回调的闭包陷阱

当前addCard函数在socket回调中使用的是旧的props.opponent(闭包陷阱),修改为直接使用最新props:

useEffect(() => {
  const handleCardAdd = (name) => {
    if(name === props.opponent){
      setCards(cards => cards +1)
    }
  };
  props.socket.on('getCardFromDeckToOpponentResponse', handleCardAdd);
  
  // 清理函数,防止内存泄漏
  return () => {
    props.socket.off('getCardFromDeckToOpponentResponse', handleCardAdd);
  };
}, [props.socket, props.opponent]); // 添加props.opponent到依赖项

方案三:父组件添加条件渲染,避免传递undefined

在父组件中,只有当opponents数组长度足够时才渲染对应的子组件,避免传递undefined:

return (
  <div className={numberOfPlayers+'-players'}>
    {opponents.length >= 2 && (
      <div className="opponent one">
        <OpponentCards opponent={opponents[1]} number={1} socket={props.socket}/>
      </div>
    )}
    {opponents.length >= 3 && (
      <div className="opponent two">
        <OpponentCards opponent={opponents[2]} number={3} socket={props.socket}/>
      </div>
    )}
    {opponents.length >= 4 && (
      <div className="opponent three">
        <OpponentCards opponent={opponents[3]} number={3} socket={props.socket}/>
      </div>
    )}
  </div>
)

(注:原代码中第三个OpponentCards误用了opponents[2],这里修正为opponents[3])

内容的提问来源于stack exchange,提问作者Dimon 1200

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:35:33