【问题标题】:How to avoid firing multiple redux actions with real time firestore listeners如何避免使用实时 Firestore 侦听器触发多个 redux 操作
【发布时间】:2021-05-23 21:00:59
【问题描述】:

简介

一点警告:我确实使用 Redux Toolkit

我有一堆列表,其中一个应该是活动的。并且根据某些上下文,active 列表应该有所不同。 例如我有 3 个列表(A、B、C),让我们看看以下模式:

  1. 列表 B 处于活动状态,我决定创建一个新列表。创建 list D 后,list D 应该处于活动状态:
    • 列表 D - 有效
    • 列表 C
    • 列表 B
    • 列表 A
  2. 列表 B 处于活动状态,我决定更改页面。当我回来时,列表 B 应该像更改页面之前一样处于活动状态。

问题

当我从一开始就启动 setListsAction 时,它总是监听 firestore 并在我每次操作存储(添加、删除、更新)时被调用,然后将所有数据传递给减速器。因此,我无法控制实际执行了哪个操作。对于我的 setListsReducer 中的这种情况,我检查是否已经有一个活动列表,如果有,我不会更改它(在 examples 部分中涵盖我的第二个模式)。但是,使用这样的逻辑,我无法将 newly created list 设置为活动的,因为总会有一个活动的列表,这就是为什么在我的 createListAction 中我传递了一个新创建的列表到有效负载并在 createListReducer 我将有效负载设置为活动列表。但是,这种方法的警告是 setListsActioncreateListAction 都会被触发,因此 redux 状态会连续更新两次,因此我的组件不需要重新渲染。 循环是这样的

  • createListAction 我将列表添加到 firestore
  • firestore 已更新,因此 setListsAction 被触发
  • createListAction 调度已完成的操作。

我的代码

动作

setListsAction

export const subscribeListsAction = () => {
  return async (dispatch) => {
    dispatch(fetchLoadingActions.pending());
    const collection = await db.collection('lists');
    const unsubscribe = collection
      .onSnapshot((querySnapshot) => {
        const lists = querySnapshot.docs.map((doc) => {
          const list = { ...doc.data(), id: doc.id };
          return list;
        });

        dispatch(
          fetchLoadingActions.fulfilled({
            lists,
          })
        );
      });
  };
};

createListAction

export const createListActionAsync = (list) => {
  return async (dispatch: Dispatch<PayloadAction<any>>) => {
    dispatch(listsLoadingActions.pending());
    const docList = await db.collection('lists').add(list);
    const fbList = { ...list, id: docList.id };
    dispatch(listsLoadingActions.fulfilled(fbList));
  };
};

减速器

setListsReducer

builder.addCase(fetchLoadingActions.fulfilled, (state, { payload }) => {
      state.lists = payload.lists;
      const activeList = state.activeList
        ? payload.lists.find((l) => l.id === state.activeList.id)
        : payload.lists[0];

      state.activeList = activeList;
    });

createListReducer

builder.addCase(listsLoadingActions.fulfilled, (state, { payload }) => {
      state.activeList = payload;
    });

总和

所以我希望你提出一个更好的方法来处理我的问题。我尝试在 docChanges 上使用更改类型来解决它,但是当我初始化 setListsAction 时,所有文档的更改都是 added 类型,解决方法可能会损坏应用程序的进一步实现.可能,我需要放弃实时数据库,改用get方法。

【问题讨论】:

    标签: reactjs firebase redux google-cloud-firestore


    【解决方案1】:

    如果你去掉createListReducerlistLoadingActions,你应该可以在ListsAction 钩子里面做任何事情。使用await db.collection('lists').add(list) 应该在lists 集合成功添加到数据库后重新触发侦听器。

    export const subscribeListsAction = () => {
      return async (dispatch) => {
        dispatch(fetchLoadingActions.pending());
        const collection = db.collection('lists'); // no need to await?
        let firstLoad = true; // used to determine whether to use docs or docsChanges
        const unsubscribe = collection
          .onSnapshot((querySnapshot) => {
            if (firstLoad) {
              const lists = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
              firstLoad = false;
              // Get and set initial active list?
              dispatch(
                fetchLoadingActions.fulfilled({
                  lists,
                })
              );
            } else {
              // optionally fire dispatch(fetchLoadingActions.pending()) again?
              const listsCopy = [...state.lists]; // copy the existing list to mutate it
              let activeList = state.activeList; // store the current activeList
              querySnapshot.docChanges().map((change) => {
                if (change.type === "added") {
                  const thisList = { ...change.doc.data(), id: change.doc.id };
                  listsCopy.splice(change.newIndex, 0, thisList);
                  activeList = thisList;
                } else if (change.type === "modified") {
                  listsCopy.splice(change.oldIndex, 1);
                  listsCopy.splice(change.newIndex, 0, { ...change.doc.data(), id: change.doc.id });
                } else if (change.type === "removed") {
                  listsCopy.splice(change.oldIndex, 1);
                  if (activeList.id === change.doc.id) {
                    // the current active list was removed!
                    activeList = undefined;
                  }
                }
              });
              dispatch(
                fetchLoadingActions.fulfilled({
                  lists: listsCopy,
                  activeList: activeList || listsCopy[0] // use activeList or fallback to first list in listsCopy, could still be undefined if listsCopy is empty!
                })
              );
            }
          });
        return unsubscribe;
      };
    };
    

    关于活动列表历史记录,您可以使用 URL ?list=some-id 将所选列表与 History API 一起存储,或者您可以将一个名为 activeListHistory 的数组存储在您的状态变量中,其中您可以使用 push() 和 @987654331 @ 必要时给它(确保处理旧列表不再存在且数组中没有条目的情况)。

    【讨论】:

    • 感谢您的努力!不幸的是,这样的代码不起作用,因为我在安装时订阅并在卸载时取消订阅,所以如果我更改页面然后立即返回,firstLoad 将是真的,如果我将它移出操作范围,所有列表都会有added 更改类型。我认为如果我跟踪使用added 类型进行了多少更改,则可以解决此问题,但听起来不太正确。
    • @h1kiga 很好,并且受此代码支持。所有firstLoad==true 所做的不是使用snapshot.docChanges() 并在每个文档上检查change.type(因为它将始终是"added")并且必须将每个文档拼接到原始数组,它是使用@987654339 的快捷方式@ 和 map() 以提高性能。
    • 过了一会儿,我按预期做了所有事情。非常感谢!关于您建议的历史记录,我已将 previousList 添加到状态并将其与具有相同 id 的商店中的列表合并,并且效果很好。
    猜你喜欢
    • 1970-01-01
    • 2016-09-05
    • 2018-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 2014-08-03
    相关资源
    最近更新 更多