React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器
Posted
技术标签:
【中文标题】React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器【英文标题】:React Hooks + Firebase Firestore onSnapshot - Correct use of a firestore listener with react hooks 【发布时间】:2021-03-30 18:47:45 【问题描述】:问题
假设您有一个带有数据库侦听器的屏幕,该侦听器是在 useEffect 中创建的,该侦听器的目的是增加屏幕中的一个计数器,就像这样:
(仅使用 useEffect 没有依赖的钩子)
function MyScreen(props)
const [magazinesCounter, setMagazinesCounter] = useState(0);
const handleMagazinesChanges = (snapshot) =>
const changes = snapshot.docChanges();
let _magazinesCounter = magazinesCounter;
changes.forEach((change) =>
if (change.type === "added")
_magazinesCounter += 1;
if (change.type === "removed")
_magazinesCounter -= 1;
);
setMagazinesCounter(_magazinesCounter);
;
useEffect(() =>
const query = props.db.collection("content")
.where("type", "==", "magazine");
// Create the DB listener
const unsuscribe = query.onSnapshot(handleMagazinesChanges, (err) => );
return () =>
unsuscribe();
, []);
正如您在此处看到的,这将不起作用,因为在状态更新时不会重新创建 useEffect 的侦听器中使用的 handleMagazinesChanges...
因此,我尝试解决将magazinesCounter 作为依赖项传递给useEffect 的问题,如下所示:
(使用 useEffect hook 将修改后的状态作为依赖)
useEffect(() =>
// ... the same stuff
, [magazinesCounter]);
但是这样一来,我们将进入一个无限循环,因为监听器将被重新创建,并且
if (change.type === "added")
_magazinesCounter += 1;
...
setMagazinesCounter(_magazinesCounter);
将再次执行,一次又一次......
Pd:另外,将 handleMagazinesChanges 函数包装在 useCallback 中,并以 MagazinesCounter 作为依赖项,然后将该函数作为依赖项传递给 useEffect,将具有相同的效果...
那么,我该如何解决这种情况?我知道,如果我们使用 useRef 对同一数据进行辅助引用,我们可以成功执行此操作,避免死循环。但是,有没有其他更好的方法来做到这一点?我的意思是,没有状态 + 对最新数据的引用:
(无依赖的useEffect + useRef)
// This works good, but is there any other better solution to this problem?
function MyScreen(props)
const [magazinesCounter, setMagazinesCounter] = useState(0);
const magazinesCounterRef = useRef(magazinesCounter);
const handleMagazinesChanges = (snapshot) =>
const changes = snapshot.docChanges();
changes.forEach((change) =>
if (change.type === "added")
magazinesCounterRef.current += 1;
if (change.type === "removed")
magazinesCounterRef.current -= 1;
);
setMagazinesCounter(magazinesCounterRef.current);
;
useEffect(() =>
const query = props.db.collection("content")
.where("type", "==", "magazine");
// Create the DB listener
const unsuscribe = query.onSnapshot(handleMagazinesChanges, (err) => );
return () =>
unsuscribe();
, []);
有什么想法吗?谢谢。
Pd:也许这是要走的路,但我想有更好的方法,而无需创建辅助变量。
【问题讨论】:
【参考方案1】:为了实现这一目标,我有两条建议:
-
将回调移动到
useEffect
内以避免每次渲染时重新创建函数
在设置状态时使用回调来获取当前值(参见React Docs here)以避免在状态改变时需要重新创建回调
将这些与您当前的代码一起使用:
function MyScreen(props)
const [magazinesCounter, setMagazinesCounter] = useState(0);
useEffect(() =>
// Moved inside "useEffect" to avoid re-creating on render
const handleMagazinesChanges = (snapshot) =>
const changes = snapshot.docChanges();
// Accumulate differences
let difference = 0;
changes.forEach((change) =>
if (change.type === "added")
difference += 1;
if (change.type === "removed")
difference -= 1;
);
// Use the setState callback
setMagazinesCounter((currentMagazinesCounter) => currentMagazinesCounter + difference);
;
const query = props.db.collection("content")
.where("type", "==", "magazine");
// Create the DB listener
const unsuscribe = query.onSnapshot(handleMagazinesChanges,
err => console.log(err));
return () =>
unsuscribe();
, []);
【讨论】:
以上是关于React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器的主要内容,如果未能解决你的问题,请参考以下文章
React Hooks 不适用于 Firebase Cloud Functions 错误:不变违规:无效的钩子调用
React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器