我通过构造一个跟踪某些内部状态的对象来进行分页侦听器,并具有 PageBack、PageForward、ChangeLimit 和 Unsubscribe 方法。对于Listeners,最好取消订阅之前的listener,重新设置一个;这段代码就是这样做的。在这里添加一个层可能更有效:使用 有点 更大的页面从 Firestore 进行分页(稍微计算昂贵的设置和拆除)权衡实际获取的记录数(实际成本),以及然后在本地提供较小的页面。但是,对于 PaginatedListener:
/**
* ----------------------------------------------------------------------
* @function filterQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* @param {Query} query collectionReference or Query to build filter upong
* @param {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* @returns Firestor Query object
*/
export const filterQuery = (query, filterArray = null) => {
return filterArray
? filterArray.reduce((accQuery, filter) => {
return accQuery.where(filter.fieldRef, filter.opStr, filter.value);
}, query)
: query;
};
/**
* ----------------------------------------------------------------------
* @function sortQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* @param {Query} query collectionReference or Query to build filter upong
* @param {array} sortArray an (optional) 2xn array of sort (i.e. "orderBy") conditions
* @returns Firestor Query object
*/
export const sortQuery = (query, sortArray = null) => {
return sortArray
? sortArray.reduce((accQuery, sortEntry) => {
return accQuery.orderBy(sortEntry.fieldRef, sortEntry.dirStr || "asc");
//note "||" - if dirStr is not present(i.e. falsy) default to "asc"
}, query)
: query;
};
/**
* ----------------------------------------------------------------------
* @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 {enum} status status of pagination object
*
* @method PageForward Changes the listener to the next page forward
* @method PageBack Changes the listener to the next page backward
* @method Unsubscribe returns the unsubscribe function
* ----------------------------------------------------------------------
*/
export class PaginatedListener {
_setQuery = () => {
const db = this.ref ? this.ref : fdb;
this.Query = sortQuery(
filterQuery(db.collection(this.table), this.filterArray),
this.sortArray
);
return this.Query;
};
/**
* ----------------------------------------------------------------------
* @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 {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* @param {array} sortArray a 2xn array of sort (i.e. "orderBy") conditions
* @param {ref} ref (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 {function} dataCallback
* @param {function} errCallback
* **********************************************************/
constructor(
table,
filterArray = null,
sortArray,
ref = null,
limit = PAGINATE_DEFAULT,
dataCallback = null,
errCallback = null
) {
this.table = table;
this.filterArray = filterArray;
this.sortArray = sortArray;
this.ref = ref;
this.limit = limit;
this._setQuery();
/*this.Query = sortQuery(
filterQuery(db.collection(this.table), this.filterArray),
this.sortArray
);*/
this.dataCallback = dataCallback;
this.errCallback = errCallback;
this.status = PAGINATE_INIT;
}
/**
* @method PageForward
* @returns Promise of a QuerySnapshot
*/
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(
this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
},
(err) => {
this.errCallback(err);
}
);
return this.unsubscriber;
};
/**
* @method PageBack
* @returns Promise of a QuerySnapshot
*/
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(
this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
},
(err) => {
this.errCallback(err);
}
);
return this.unsubscriber;
};
/**
* @method ChangeLimit
* sets page size limit to new value, and restarts the paged listener
* @param {number} newLimit
* @returns Promise of a QuerySnapshot
*/
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(
this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
},
(err) => {
this.errCallback(err);
}
);
return this.unsubscriber;
};
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(
this.snapshot.empty
? null
: this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
},
(err) => {
this.errCallback(err);
}
);
return this.unsubscriber;
};
unsubscribe = () => {
//IF unsubscribe function is set, run it.
this.unsubscriber && this.unsubscriber();
this.unsubscriber = null;
};
}