React+Firebase Firestore+Reactfire中useFirestoreDocData触发无限循环问题
问题根因
无限重渲染的核心原因是每次组件渲染都会生成全新的Firestore文档引用,触发useFirestoreDocData不断重新订阅,进而反复触发重渲染形成循环:
- 组件顶层执行
useFirestore().collection('GameLobbies').doc()时,无参调用doc()会每次生成一个带随机ID的全新文档引用对象 useFirestoreDocData只要检测到传入的文档引用变化,就会重新执行订阅逻辑,返回新的status和data,这两个状态变化会触发组件重渲染- 重渲染后又会生成新的文档引用,进入下一轮循环,形成死循环
修复方案
用useMemo把文档引用的创建逻辑包裹,保证整个组件生命周期内只会生成一次引用,破坏循环触发的条件。同时补全useEffect的依赖项,避免闭包陷阱。
修改后完整代码:
import * as React from 'react'; import { useFirestore, useFirestoreDocData, useUser } from 'reactfire'; import 'firebase/firestore'; import 'firebase/auth'; import { useEffect, useState, useMemo } from 'react'; import { Link, useHistory } from 'react-router-dom'; interface ICreateGameProps { } interface IGameLobbyDoc { playerOne: string; playerTwo: string | null; } const CreateGame: React.FunctionComponent<ICreateGameProps> = (props) => { const user = useUser(); const firestore = useFirestore(); // 用useMemo保证只生成一次文档引用 const gameLobbyDocRef = useMemo(() => { return firestore.collection('GameLobbies').doc() }, [firestore]) const { status, data } = useFirestoreDocData<IGameLobbyDoc>(gameLobbyDocRef); const [newGameId, setNewGameId] = useState('') const history = useHistory(); // 补全依赖项,确保用户加载完成后能正常创建文档 useEffect(() => { async function createGameLobby() { if (!user.data?.uid) return const gl: IGameLobbyDoc = { playerOne: user.data.uid, playerTwo: null } await gameLobbyDocRef.set(gl) setNewGameId(gameLobbyDocRef.id) } createGameLobby() return () => { gameLobbyDocRef.delete(); } }, [user.data?.uid, gameLobbyDocRef]) // 增加用户加载态,避免无意义的渲染 if (user.status === 'loading') return <p>加载中...</p> return <> <h2>Gameid : {newGameId}</h2> <p>Waiting for second player to join...</p> <Link to="/">Go Back</Link> </> }; export default CreateGame;
额外优化建议
- 如果不需要在文档创建后实时监听文档变化,可把订阅逻辑放到文档创建成功之后再执行,避免多余的空文档查询
- 可增加文档创建失败的异常捕获逻辑,提升稳定性
内容的提问来源于stack exchange,提问作者Boris Grunwald
相关产品推荐
相关产品推荐

