如何传入更新后的参数重新调用useCollection自定义Hook

问题描述
Home组件首次挂载时会传入初始参数调用useCollection Hook实现列表数据拉取,现在需要传入不同参数重新触发Hook执行,实现列表过滤功能。
实际操作中两个方案都失败:
- 在按钮onClick事件回调里直接调用Hook,直接抛出错误
- 用useState更新传入Hook的参数,无法触发Hook重新执行查询
现有useCollection Hook源码
import React, { useEffect, useRef, useState } from "react"; import { projectFirestore } from "../firebase/config"; export const useCollections = (collection, _query, _orderBy) => { const [documents, setDocuments] = useState(null); const [error, setError] = useState(null); const query = useRef(_query).current; const orderBy = useRef(_orderBy).current; useEffect(() => { let ref = projectFirestore.collection(collection); if (query) { ref = ref.where(...query); } if (orderBy) { ref = ref.orderBy(...orderBy); } const unsubscribe = ref.onSnapshot( (snapshot) => { let results = []; snapshot.docs.forEach((doc) => { results.push({ ...doc.data(), id: doc.id }); }); setDocuments(results); setError(null); }, (error) => { console.log(error); setError("could not fetch data"); } ); return () => unsubscribe(); }, [collection, query, orderBy]); return { documents, error }; };
核心原因
两个问题分别对应React Hook的基础规则和现有Hook的实现bug:
- 违反Hook调用规则:Hook只能在组件/自定义Hook的顶层作用域调用,事件回调、条件判断、循环里都不能直接调用Hook,React无法在这些上下文里正确追踪Hook的状态链路,所以会直接抛错。
- Hook内部ref写法错误:
useRef(_query).current只会在组件首次渲染时把传入的_query存为ref初始值,后续父组件传入新的查询参数时,ref保存的值不会同步更新,导致useEffect依赖的query/orderBy永远是初始值,参数变化不会触发effect重跑。
实现步骤
1. 修复自定义Hook的参数响应逻辑
去掉错误的ref固定取值写法,对引用类型的查询参数做序列化处理避免依赖项误触发,修改后的Hook代码:
import React, { useEffect, useState } from "react"; import { projectFirestore } from "../firebase/config"; export const useCollections = (collection, _query, _orderBy) => { const [documents, setDocuments] = useState(null); const [error, setError] = useState(null); // 序列化数组/对象类型参数,既保证值变化能被依赖检测到,又避免引用类型每次渲染生成新引用导致的重复执行 const queryStr = JSON.stringify(_query); const orderByStr = JSON.stringify(_orderBy); useEffect(() => { const query = JSON.parse(queryStr); const orderBy = JSON.parse(orderByStr); let ref = projectFirestore.collection(collection); if (query) { ref = ref.where(...query); } if (orderBy) { ref = ref.orderBy(...orderBy); } const unsubscribe = ref.onSnapshot( (snapshot) => { let results = []; snapshot.docs.forEach((doc) => { results.push({ ...doc.data(), id: doc.id }); }); setDocuments(results); setError(null); }, (error) => { console.log(error); setError("数据拉取失败"); } ); return () => unsubscribe(); }, [collection, queryStr, orderByStr]); return { documents, error }; };
2. 业务组件中用state维护查询参数
永远在组件顶层调用Hook,通过更新state的方式触发Hook参数变化,示例代码:
import { useState } from 'react' import { useCollections } from '你的Hook路径/useCollections' const Home = () => { // 用state维护查询条件,初始值为默认过滤规则,不需要默认过滤就传null const [listQuery, setListQuery] = useState(null) // 顶层调用Hook,不要放到事件、条件判断里 const { documents: list, error } = useCollections('posts', listQuery, ['createTime', 'desc']) // 点击按钮只更新查询参数state,Hook会自动感知参数变化重跑查询 const handleFilterTech = () => { setListQuery(['category', '==', 'tech']) } const handleReset = () => { setListQuery(null) } return ( <div className="home"> <div className="filter-bar"> <button onClick={handleFilterTech}>只看技术类内容</button> <button onClick={handleReset}>重置筛选</button> </div> {error && <p className="error">{error}</p>} {list && ( <div className="list"> {list.map(item => ( <div key={item.id} className="list-item">{item.title}</div> ))} </div> )} </div> ) } export default Home
如果需要多条件查询,直接把查询条件组装为符合Firestore要求的格式传入即可,多条件时可以把where条件做循环遍历处理,适配多个查询规则。
内容的提问来源于stack exchange,提问作者Shivang Mathur
相关产品推荐
相关产品推荐

