【问题标题】:Firebase - Detach/Unsubscribe from database : Multiple listeners at a timeFirebase - 从数据库中分离/取消订阅:一次多个侦听器
【发布时间】:2021-08-30 15:01:49
【问题描述】:

我有一个星期的时间。 周有一个data-week-id作为它的属性,一周内的每一天都有它自己的data-day-id.

我有一个功能可以实时监听几天内的任何更新,例如当某天添加任务时。我还有一个每周生成的函数,删除旧的并使用新的data-week-iddata-day-id 生成一个新函数。但是每次我回到我已经生成并添加新任务的一周时,它都会渲染多次,具体取决于创建侦听器的次数,何时调用 renderTasks 函数。

我似乎无法弄清楚如何在未来一周或过去一周内取消订阅之前生成的一周。

这是我的代码:

function generateWeek(monday) {
    $(".week").children().remove();
    for (let i = 0; i < 7; i++) {
        let dayDate = moment()
            .isoWeekday(i + monday)
            .format("dddd, DD.MM");
        let dayId = moment()
            .isoWeekday(i + monday)
            .format("DD-MM-YYYY");

        const day = `
            <div class="day" data-day-id="${dayId}">
                <div class="day__head">
                    <span class="day__date">${dayDate}</span>
                    <div class="day__badges"></div>
                </div>
                <div class="day__body">
                    <div class="day__tasks-wrapper"></div>
                </div>
            </div>`;

        $(".week").append(day);
    }
    renderTasks();
}

function renderTasks() {
    let week = $(".week").attr("data-week-id");
    for (i = 0; i < 7; i++) {
        let day = $(this).attr("data-day-id");
        unsubscribe = db.collection(`users/${auth.currentUser.uid}/weeks/${week}/${day}`)
            .orderBy("createdAt", "asc")
            .onSnapshot((snapshot) => {
                let changes = snapshot.docChanges();
                changes.forEach((change) => {
                    switch (change.type) {
                        case "added":
                            let task = `
                                <div class="${change.doc.data().completed ? "task completed" : "task"}" data-task-id="${change.doc.id}">
                                    <div class="task__text">
                                        <span>${change.doc.data().title} | ${change.doc.id}</span>
                                    </div>
                                    <div class="task__time">${change.doc.data().time}</div>
                                </div>`;
                            $(`.day[data-day-id="${day}"]`).find(".day__tasks-wrapper").prepend(task);
                            break;
                        case "removed":
                            $(`.task[data-task-id="${change.doc.id}"]`).remove();
                            break;
                        case "modified":
                            $(`.task[data-task-id="${change.doc.id}"]`).toggleClass("completed", change.doc.data().completed);
                            $(`.task[data-task-id="${change.doc.id}"]`).find(".task__text span").text(change.doc.data().title);
                            $(`.task[data-task-id="${change.doc.id}"]`).find(".task__time").text(change.doc.data().time);
                            break;
                    }
                });
            });
    });
}

TL;DR:帮我弄清楚如何在生成新的一周时分离在renderTasks 函数中创建的侦听器。

【问题讨论】:

    标签: javascript jquery firebase firebase-realtime-database google-cloud-firestore


    【解决方案1】:

    基本答案:附加监听器最初会返回取消订阅函数 - 调用它来分离监听器。

    更长的答案:我有一个即将发布的包,@leaddreamer/firebase-wrapper,其中包括一个 PaginatedListener 创建者-有一些方法可以在您页面时处理侦听器的分离/重新连接(因为我仍然摇晃它,直到我到达 1.1.x 版本之前我不会在项目中使用它——但这里的例子是已知的):

     * @class
     * @static
     * @classdesc An object to allow for paginating a listener for table read from Firestore.
     * REQUIRES a sorting choice
     * masks some subscribe/unsubscribe action for paging forward/backward
     * @property {Query} Query that forms basis for the table read
     * @property {number} limit page size
     * @property {QuerySnapshot} snapshot last successful snapshot/page fetched
     * @property {PagingStatus} status status of pagination object
     *
     * @property {function} PageForward Changes the listener to the next page forward
     * @property {function} PageBack Changes the listener to the next page backward
     */
    export class PaginatedListener {
      /**
       * @member {string} table
       * @memberof PaginatedListener
       */
      //table = null;
      /**
       * @member {filterObject} [filterArray]
       * @memberof PaginatedListener
       */
      //filterArray = null;
      /**
       * @member {sortObject} [sortArray]
       * @memberof PaginatedListener
       */
      //sortArray = null;
      /**
       * @member {string} refPath
       * @memberof PaginatedListener
       */
      //refPath = null;
      /**
       * @member {Query} Query
       * @memberof PaginatedListener
       */
      //Query = null;
      /**
       * @member {number} limit
       * @memberof PaginatedListener
       */
      //limit = PAGINATE_DEFAULT;
      /**
       * @member {QuerySnapshot} snapshot
       * @memberof PaginatedListener
       */
      //snapshot = null;
      /**
       * @member {RecordListener} dataCallback
       * @memberof PaginatedListener
       */
      //dataCallback = null;
      /**
       * @member {callback} errCallback
       * @memberof PaginatedListener
       */
      /**
       * @member {number} status
       * @memberof PaginatedListener
       */
      //status = null; // -1 pending; 0 uninitialized; 1 updated;
      /**
       * @constructs PaginatedListener constructs an object to paginate through large
       * Firestore Tables
       * @param {!string} table a properly formatted string representing the requested collection
       * - always an ODD number of elements
       * @param {filterObject} [filterArray] an (optional) 3xn array of filter(i.e. "where") conditions
       * @param {!sortObject} [sortArray] a 2xn array of sort (i.e. "orderBy") conditions
       * @param {?refPath} refPath (optional) allows "table" parameter to reference a sub-collection
       * of an existing document reference (I use a LOT of structered collections)
       *
       * The array is assumed to be sorted in the correct order -
       * i.e. filterArray[0] is added first; filterArray[length-1] last
       * returns data as an array of objects (not dissimilar to Redux State objects)
       * with both the documentID and documentReference added as fields.
       * @param {?number} limit (optional)
       * @param {!callback} dataCallback
       * @param {!callback} errCallback
       */
      constructor(
        table,
        filterArray = null,
        sortArray,
        refPath = null,
        limit = PAGINATE_DEFAULT,
        dataCallback = null,
        errCallback = null
      ) {
        this.table = table;
        this.filterArray = filterArray;
        this.sortArray = sortArray;
        this.refPath = refPath;
        this.limit = limit;
        this._setQuery();
        this.dataCallback = dataCallback;
        this.errCallback = errCallback;
        this.status = PAGINATE_INIT;
      }
    
      /**
       * @private
       * @method _setQuery
       * @methodof PaginatedListener
       * @description reconstructs the underlying query
       * @returns {Query}
       */
      _setQuery() {
        const db = this.refPath ? this.refPath : fdb;
        this.Query = sortQuery(
          filterQuery(db.collection(this.table), this.filterArray),
          this.sortArray
        );
        return this.Query;
      }
    
      /**
       * @method PageBack
       * @memberof PaginatedListener
       * @description resets the listener query to the previous page of results.
       * Unsubscribes from the current listener, constructs a new query, and sets it\
       * as the new listener
       * @returns {function} returns the unsubscriber function (for lifecycle events)
       */
      PageForward() {
        const runQuery =
          this.unsubscriber && !this.snapshot.empty
            ? this.Query.startAfter(last(this.snapshot.docs))
            : this.Query;
    
        //IF unsubscribe function is set, run it.
        this.unsubscriber && this.unsubscriber();
    
        this.status = PAGINATE_PENDING;
    
        this.unsubscriber = runQuery.limit(Number(this.limit)).onSnapshot(
          (QuerySnapshot) => {
            this.status = PAGINATE_UPDATED;
            //*IF* documents (i.e. haven't gone back ebfore start)
            if (!QuerySnapshot.empty) {
              //then update document set, and execute callback
              this.snapshot = QuerySnapshot;
            }
            this.dataCallback(RecordsFromSnapshot(this.snapshot));
          },
          (err) => {
            this.errCallback(err);
          }
        );
        return this.unsubscriber;
      }
    
      /**
       * @method PageBack
       * @memberof PaginatedListener
       * @description resets the listener query to the next page of results.
       * Unsubscribes from the current listener, constructs a new query, and sets it\
       * as the new listener
       * @returns {function} returns the unsubscriber function (for lifecycle events)
       */
      PageBack() {
        const runQuery =
          this.unsubscriber && !this.snapshot.empty
            ? this.Query.endBefore(this.snapshot.docs[0])
            : this.Query;
    
        //IF unsubscribe function is set, run it.
        this.unsubscriber && this.unsubscriber();
    
        this.status = PAGINATE_PENDING;
    
        this.unsubscriber = runQuery.limitToLast(Number(this.limit)).onSnapshot(
          (QuerySnapshot) => {
            //acknowledge complete
            this.status = PAGINATE_UPDATED;
            //*IF* documents (i.e. haven't gone back ebfore start)
            if (!QuerySnapshot.empty) {
              //then update document set, and execute callback
              this.snapshot = QuerySnapshot;
            }
            this.dataCallback(RecordsFromSnapshot(this.snapshot));
          },
          (err) => {
            this.errCallback(err);
          }
        );
        return this.unsubscriber;
      }
    
      /**
       * @method ChangeLimit
       * @memberof PaginatedListener
       * @description sets page size limit to new value, and restarts the paged listener
       * @param {number} newLimit
       * @returns {function} returns the unsubscriber function (for lifecycle events)
       */
      ChangeLimit(newLimit) {
        const runQuery = this.Query;
    
        //IF unsubscribe function is set, run it.
        this.unsubscriber && this.unsubscriber();
    
        this.limit = newLimit;
    
        this.status = PAGINATE_PENDING;
    
        this.unsubscriber = runQuery.limit(Number(this.limit)).onSnapshot(
          (QuerySnapshot) => {
            this.status = PAGINATE_UPDATED;
            //*IF* documents (i.e. haven't gone back ebfore start)
            if (!QuerySnapshot.empty) {
              //then update document set, and execute callback
              this.snapshot = QuerySnapshot;
            }
            this.dataCallback(RecordsFromSnapshot(this.snapshot));
          },
          (err) => {
            this.errCallback(err);
          }
        );
        return this.unsubscriber;
      }
    
      /**
       * @method ChangeFilter
       * @memberof PaginatedListener
       * @description changes the filter on the subscription
       * This has to unsubscribe the current listener,
       * create a new query, then apply it as the listener
       * @param {filterObject} [filterArray] an array of filter descriptors
       * @returns {function} returns the unsubscriber function (for lifecycle events)
       */
      ChangeFilter(filterArray) {
        //IF unsubscribe function is set, run it (and clear it)
        this.unsubscriber && this.unsubscriber();
    
        this.filterArray = filterArray; // save the new filter array
        const runQuery = this._setQuery(); // re-build the query
        this.status = PAGINATE_PENDING;
    
        //fetch the first page of the new filtered query
        this.unsubscriber = runQuery.limit(Number(this.limit)).onSnapshot(
          (QuerySnapshot) => {
            this.status = PAGINATE_UPDATED;
            //*IF* documents (i.e. haven't gone back ebfore start)
            this.snapshot = QuerySnapshot;
            this.dataCallback(RecordsFromSnapshot(this.snapshot));
          },
          (err) => {
            this.errCallback(err);
          }
        );
        return this.unsubscriber;
      }
    
      /**
       * @method unsubscribe
       * @memberof PaginatedListener
       * @description IF unsubscribe function is set, run it.
       */
      unsubscribe() {
        //IF unsubscribe function is set, run it.
        this.unsubscriber && this.unsubscriber();
        this.unsubscriber = null;
      }
    }
    

    【讨论】:

    • 我阅读了有关unsubscribe() 返回函数的文档,但我似乎无法让它适用于我编写的代码。你能介绍一下我需要如何编写这个函数来从所有的日子里移除听众吗?
    • 对不起,不,我不能为你做那么多。为什么不显示演示问题所需的 MINIMUM 代码,也许有人可以提供帮助?
    【解决方案2】:

    终于找到问题所在了,我们开始吧:

    正如您在renderTasks 函数中所见,我正在为一周中的每一天创建一个实时监听器。在 firesbase 文档中,他们说,每当您想取消订阅侦听器时,只需调用您将其命名为的变量;这会返回一个最终让您取消订阅的函数。

    我做错的事情是,我没有注意到循环再次运行时,我的unsubscribe 会被覆盖,导致仅从一周的最后一天 - 星期日退订。

    为了解决这个问题,我定义了一个空的unsubscribeArray,它在unsubscribe 变量声明之后将函数推入其中。这样,当循环结束时,我们将所有取消订阅的侦听器都存储在数组中。

    function renderTasks(){
        // Rest of the code remains the same,
        // but I added this line after the unsubscribe variable declaration.
        unsubscribeArray.push(unsubscribe)
    
    }
    

    当我们想取消订阅时,我们调用 unsubscribeFromPreviousWeek - 一个循环通过 unsubscribeArray 并从先前生成的一周中删除所有侦听器的函数 - 立即在 generateWeek 函数中:

    function generateWeek(monday) {
        unsubscribeFromPreviousWeek();
        // Rest of the code remains the same.
    }
    

    unsubscribeFromPreviousWeek 函数的工作方式如下:

    function unsubscribeFromPreviousWeek(){
        for (i = 0; i < unsubscribeArray.length; i++) {
            unsubscribeArray[i] && unsubscribeArray[i]();
        }
        unsubscribeArray = [];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-13
      • 2021-02-27
      • 2020-07-05
      • 2021-08-13
      • 2021-03-16
      • 1970-01-01
      • 1970-01-01
      • 2020-10-17
      相关资源
      最近更新 更多