为什么自定义Hook搭配Context、useEffect会触发渲染与Firebase请求无限循环?
问题根因分析
导致无限循环+无限请求Firebase的问题一共两个核心触发点:
1. useProjects自定义Hook的useEffect依赖逻辑错误
你把projects作为useEffect的依赖项,直接形成了闭环逻辑:
- 每次
projects状态变化都会触发useEffect重新执行 - 执行后发起Firebase请求,请求返回后你用
JSON.stringify对比两个数组的判断本身不可靠,只要对比判定值不同,就会触发setProjects更新projects状态 - 状态更新后又再次触发useEffect执行,形成请求→更新状态→再请求→再更新的无限循环
额外提醒:用JSON.stringify对比对象数组是不可靠的,若两个对象属性顺序不同、存在undefined/function等不可序列化值时,会出现内容相同但判定为不等的情况,也会额外触发状态更新
2. Projects组件列表项key值使用Math.random()加剧重渲染
列表的key属性是React用来判断列表项是否需要复用更新的核心标识:
- 你每次渲染都给每个li生成随机的key,等于每次渲染React都认为所有列表项是全新的元素,会销毁之前的所有列表项重新渲染
- 结合Context的状态传递机制,每次重渲染都会触发useProjects的重新调用,进一步放大了无限循环的问题
修复方案
修复后的useProjects Hook
把useEffect的依赖项改为空数组[],仅在Hook第一次挂载时发起请求即可,如果需要监听数据实时更新,可以替换为Firebase的onSnapshot监听方法,不要把projects作为请求触发的依赖。
export const useProjects = () => { const [projects, setProjects] = useState([]); useEffect(() => { const getProjects = async () => { try { const result = await firebase .firestore() .collection("projects") .where("userId", "==", "1234567890") .orderBy("projectId") .get(); const allProjects = result.docs.map((project) => ({ docId: project.id, ...project.data(), })); setProjects(allProjects); } catch (err) { console.log("useProjects error", err); } } getProjects(); }, []); return { projects, setProjects }; };
修复后的Projects组件
把列表项的key替换为后端返回的稳定唯一ID,不要用随机值:
const Projects = () => { const [active, setActive] = useState(null); const { setSelectedProject } = useSelectedProjectValue(); const { projects } = useProjectsValue(); return ( projects && projects.map((project) => ( <li key={project.docId} className={ active === project.projectId ? "sidebar__project active" : "sidebar__project" } > <div onClick={() => { setActive(project.projectId); setSelectedProject(project.projectId); }} > <IndividualProject className="individualProjectComp" project={project} /> </div> </li> )) ); };
内容的提问来源于stack exchange,提问作者Mahmoud
相关产品推荐
相关产品推荐

