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

Firebase onSnapshot()不触发重渲染,React多人游戏lobby玩家列表更新异常

问题根因
  • 状态更新未生成新引用:代码中let playersArr = players直接复用了原状态数组的引用,push操作属于修改原数组,没有生成新的数组实例。React对状态的浅比较会判定数组无变化,导致视图不更新。
  • 闭包陷阱:useEffect依赖为空数组,其内部onSnapshot回调捕获的players永远是初始的空数组,后续所有更新都只能拿到当前新增的单个玩家,无法累加历史玩家数据。
  • 额外隐藏问题:没有对Firebase的快照监听做卸载清理,会导致内存泄漏。
修复代码

仅需要修改createLobbyInDatabase中的快照监听逻辑即可,修复后的完整代码如下:

import Style from './NewLobby.module.css'
import React, { useEffect, useState } from "react";
import { useLocation } from 'react-router-dom';
import { db } from "../services/firestore";
import { v4 } from 'uuid'


const NewLobby = () => {
    const [players, setPlayers] = useState([]);
    const [lobby, setLobby] = useState({});
    const location = useLocation();
    const currentUserId = v4();
    const lobbiesRef = db.collection("GameLobbies");

    useEffect(() => {
        let unsubPlayers; // 声明监听取消函数
        if (location.state.objective === "createLobby") {
            console.log('create lobby')
            unsubPlayers = createLobbyInDatabase();
        } else if (location.state.objective === "joinLobby") {
            console.log('join lobby')
            findLobbyByCode();
        }
        // 组件卸载时取消监听,避免内存泄漏
        return () => unsubPlayers?.();
    }, [])

    const createLobbyInDatabase = () => {
        // 获取lobby数据
        const code = createLobbyCode();
        const lobbyId = v4();

        // 在Firebase中创建lobby
        lobbiesRef.doc(lobbyId).set({
            code: code,
            id: lobbyId,
        })

        // 在lobby文档中创建players集合并添加当前用户
        lobbiesRef.doc(lobbyId).collection("players").doc(currentUserId).set({
            name: location.state.username,
            score: 0,
            id: currentUserId
        })

        // 获取lobby信息
        lobbiesRef.doc(lobbyId).get().then(doc => {
            setLobby(doc.data());
        })

        // 实时监听lobby内玩家数据,返回取消监听函数
        return lobbiesRef.doc(lobbyId).collection("players").onSnapshot((snapshot) => {
            // 直接从快照获取所有玩家数据,不需要依赖外部状态,避免闭包问题
            const newPlayers = snapshot.docs.map(doc => doc.data());
            setPlayers(newPlayers);
        })
    }

    const findLobbyByCode = () => {
        // 待实现
    }

    const createLobbyCode = () => {
        const codeLength = 4;
        const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        let codeTemp = '';
        for (let i = 0, n = charset.length; i < codeLength; ++i) {
            codeTemp += charset.charAt(Math.floor(Math.random() * n))
        }
        return codeTemp
    }

    return (
        <section className={Style.section}>
            <p>邀请码:{lobby.code}</p>
            <p>玩家列表:</p>
            <ul>
                {players.map(player => {
                    return <li className={Style.li} key={player.id}>{player.name}</li>
                })}
            </ul>
        </section>
    );
};

export default NewLobby;
修复说明

直接从snapshot.docs遍历获取全量玩家数据,不需要处理增量变更,既避免了闭包问题,也保证每次更新都传入新的数组实例触发React的视图更新。如果有处理玩家离开、修改信息的需求,再基于docChanges的不同类型做对应处理即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 03:24:03