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
相关产品推荐
相关产品推荐

