【问题标题】:React Hooks + Firebase Firestore onSnapshot - Correct use of a firestore listener with react hooksReact Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器
【发布时间】: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:也许这是要走的路,但我想有更好的方法,而无需创建辅助变量。

【问题讨论】:

    标签: javascript reactjs react-native google-cloud-firestore react-hooks


    【解决方案1】:

    为了实现这一目标,我有两条建议:

    1. 将回调移动到 useEffect 内以避免每次渲染时重新创建函数
    2. 在设置状态时使用回调来获取当前值(参见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();
             }
         }, []);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-15
      • 2020-05-13
      • 1970-01-01
      • 1970-01-01
      • 2020-12-04
      • 2019-10-28
      • 2023-04-10
      • 1970-01-01
      相关资源
      最近更新 更多