本文介绍了React Hooks + Firebase Firestore onSnapshot-正确使用带有React Hooks的Firestore侦听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设您有一个屏幕,其中包含一个在useEffect中创建的数据库侦听器,该侦听器的目的是在屏幕上增加一个计数器,如下所示:

Imagine that you have a screen with a database listener which is created in a useEffect, and the purpose of this listener is to increment a counter in your screen, just something like this:

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.

As you can see here, this will not work because the handleMagazinesChanges which is used in the useEffect's listener is not re-created when the state updates...

因此,我尝试解决了将MagazinesCounter作为对useEffect的依赖项传递的问题,如下所示:

So, I tried to fix that passing the magazinesCounter as dependency to the useEffect, like this:

 useEffect(() => {
     // ... the same stuff
 }, [magazinesCounter]);

但是,由于我们将重新创建侦听器,因此我们将进入一个无限循环,并且

But with this, we will enter into an endless loop because the listener will be re-created, and

 if (change.type === "added") {
     _magazinesCounter  += 1;
 }
 ...
 setMagazinesCounter(_magazinesCounter);

将被再次执行...

Pd:同样,将handleMagazinesChanges函数包装在useCallback中,并将MagazinesCounter作为依赖项,然后将该函数作为依赖项传递给useEffect,将具有相同的效果...

Pd: Also, wrapping the handleMagazinesChanges function in a useCallback with the magazinesCounter as dependency, and then passing the function as dependency to the useEffect, will have the same effect...

那么,我该如何解决这种情况?我知道,如果我们对useRef使用相同的数据的辅助引用,则可以成功执行此操作,从而避免了无限循环.但是,还有其他更好的方法吗?我的意思是,没有状态+对最新数据的引用:

So, how can I fix this situation? I know that if we use an auxiliar reference to the same data with useRef, we can perform this operation succesfully, avoiding the endless loop. But, is there any other better way to do that? I mean, without having the state + a reference to the most freshed data:

// 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:也许这是要走的路,但是我想有一种更好的方法,而不创建辅助变量.

Pd: Maybe this is the way to go, but I suppose there is a better way without creating auxiliar variables.

推荐答案

要实现这一目标,我有两个建议:

There are two recommendations I would have for achieving this:

  1. 将回调移至 useEffect 内部,以避免在每个渲染器中重新创建函数
  2. 设置状态以获取当前值时使用回调(请参见反应此处的文档),以避免在状态更改时需要重新创建回调
  1. Move the callback inside of the useEffect to avoid re-creating the function each render
  2. Use a callback when setting state to get the current value (see the React Docs here) to avoid needing to re-create the callback when the state changes

在您当前的代码中使用这些代码:

Using these with your current code:

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-正确使用带有React Hooks的Firestore侦听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 11:59