基本答案:附加监听器最初会返回取消订阅函数 - 调用它来分离监听器。
更长的答案:我有一个即将发布的包,@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;
}
}