Firebase Firestore-创建快照事件侦听器是否需要过多的下载



最初,我的用例是使用快照侦听器对数据进行分页,如下所示:Firestore分页数据+快照侦听器

但那里的答案说它目前不受支持,所以我试图找到一个变通方法,我在这里找到了https://medium.com/@650egor/消防仓库-活动页面-db3afb0bf42e。这很好,但有点复杂。此外,它的下载量可能是正常情况的n倍,因为早期侦听器中的每一个变化也会被后期侦听器捕获。

所以现在,我正在考虑放弃分页。相反,每次我想获得更多数据时,我都会简单地重新创建快照侦听器,但有2倍的限制。像这样:

const [limit, setLimit] = useState(50);
const [data, setData] = useState([]);
...
useEffect(()=> {
const datalist = [];
db.collection('chats')
.where('id','==', chatId)
.limit(limit)
.onSnapshot((querySnapshot) =>{
querySnapshot.forEach((item) => datalist.push(item));
setData(datalist);
}

}, [limit]);
return <Button title="get more data" onPress={()=> { setLimit(limit * 2}} />;

我的问题是,就过度下载而言(就星火计划而言(,这糟糕吗?当我第一次做快照时,应该下载50个项目,然后第二次下载100个,然后是200个。我想确认一下它是否就是这样工作的。

此外,如果这种方法在更有趣的层面上不起作用是有原因的,我想知道。

每次执行一个不专门针对本地持久性缓存的查询时,它都会从Firestore中检索完整的文档集。这是你唯一需要知道的信息。后续查询将不会传递来自先前查询的部分缓存结果。

您现在显示的代码实际上非常有问题,因为它会泄漏侦听器。如果限制发生变化并导致钩子再次执行,那么没有任何东西可以阻止前一个侦听器。您应该从useEffect挂钩返回一个函数,该函数在不再需要时取消订阅侦听器。只需返回onSnapshot返回的取消订阅函数即可。您还应该阅读需要清理的useEffect挂钩的文档,就像您在这里所做的那样。泄露侦听器的潜在成本可能比使用新限制重复查询的成本要糟糕得多,因为泄露的侦听器会在新文档更改时不断读取这些文档——这就是为什么您必须在不再需要它们时立即取消订阅。

你确实理解得很正确。

在实施过程中,第一次读取50次,第二次读取100次,第三次读取200次,依此类推(如果文档数量小于限制,则将按文档数量计费(。

事实上,我在我发布的一个应用程序中使用了与此方法非常相似的方法,但我没有将每次加载的文档数量增加一倍,而是在限制中添加了一定的数量。

我通过构造一个跟踪某些内部状态的对象,并具有PageBack、PageForward、ChangeLimit和Unsubscribe方法,来执行分页侦听器。对于侦听器,最好取消订阅前一个侦听器并设置一个新侦听器;这个代码就是这么做的。添加一个层可能更有效,在这里:使用稍微的较大页面从Firestore分页(设置和拆除有点计算成本(,并与实际获取的记录数(实际成本(进行权衡,然后在本地提供较小的页面。但是,对于分页侦听器:

/**
* ----------------------------------------------------------------------
* @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;
};
}

最新更新