如何绑定到聚合到单个反应式数组中的多个 Firestore 查询中的实时更新

问题描述 投票:0回答:1

我想将多个 Firestore 查询的结果合并到一个文档数组中,我可以将其绑定到实时更新,就像简单的查询一样

我正在使用 Vuefire 来简化“使用”Vue 中的数据,所以理想情况下,我想要一种将一组查询发送到 Vuefire 的方法,但它不支持这一点,只支持单个查询、集合或文档。

我已经研究过 Vuefire 在底层所做的事情,即处理

onSnapshot
事件,我想我可能可以编写一个多查询版本,它使用每个查询子集的偏移量拼接到聚合数组中,但它变得很复杂我希望有一个更简单的解决方案。

我为什么要这样做?

通常,在这个特定的应用程序中,我从 Web SDK 查询 Firestore,因此可以轻松地将数据绑定到 UI 并接收更改的即时更新。不过,我现在使用的 Firestore 功能在 Web SDK(矢量搜索查询)中不可用,因此我编写了一个 Firebase 函数,它返回一组文档(或仅返回 ID)。我希望能够在用户与这组文档交互时绑定到它们(就像其他集合一样),因此我编写了一个查询,通过 ID 在 Web SDK 中重新选择这些文档。然而,还有另一个限制,即“where in”子句最多包含 30 个文档,这意味着我只能在一个查询中按 ID 选择 30 个文档。我过去已经解决了这个问题,只需将查询批处理为 30 个文档块,迭代并附加到 doc.data() 的聚合数组。这对于静态数据来说很好,但我想绑定到这些 Firestore 文档 - 理想情况下将手动聚合的查询集的结果发送到 Vuex 的

useCollection
中。

我认为这与解决类似限制的问题类似:使用 Firestore 和 geohashing 监听多个查询的实时更新

我按 id 选择文档列表的“正常”方式如下:

export async function listDocumentsByIds(collectionPath: string, ids: string[]): Promise<object[]> {
  const items:object[] = []

  const batches = slices(ids, FIRESTORE_ID_QUERY_LIMIT)
  for (const batch of batches) {
    const q = query(collection(db, collectionPath), where(documentId(), 'in', batch))
    await getDocs(q).forEach(qds => items.push({id: qds.id, ...qds.data()}))
  }
  return items
}

但正如你所看到的,我已经完成了 doc.data(),因此无法订阅文档更改。

所以我想制作一个版本,它返回一个可以订阅的块查询数组:

export async function queryDocumentsByIds(collectionPath: string, ids: string[]): Promise<Query[]> {
  const queries:Query[] = []

  const batches = slices(ids, FIRESTORE_ID_QUERY_LIMIT)
  for (const batch of batches) {
    const q = query(collection(db, collectionPath), where(documentId(), 'in', batch))
      queries.push(q)
  }

  return queries
}

理想情况下,我想将

Query[]
发送到 Vuefire 并获得一个
Ref<doc[]>
,并将所有块附加在一起(一厢情愿的想法)。

所以我正在考虑自己将数据附加到包含文档(块)数组的

Ref<doc[]>
中,然后订阅每个查询中的更改,并通过数组索引偏移量查找数组中的文档,就像
 i + c * 30
(块大小)。

类似这样的东西(未完成 - 需要找出所有索引偏移量)

export async function useQueryArray(collectionPath: string, ids: string[]){
  const multiRef = ref([])
  const queries:Query[] = await queryDocumentsByIds(collectionPath, ids)

  // listen to all changes on all queries, and merge the changes into our union data ref
  for (let i = 0; i < queries.length; i++) {
    const q = queries[i]
    const offset = i * FIRESTORE_ID_QUERY_LIMIT
    
    onSnapshot(q, (snapshot: QuerySnapshot<DocumentData, DocumentData>) => {
      snapshot.docChanges().forEach(change => {
        const { newIndex, oldIndex, doc, type } = change
        if (type === 'added') {
          multiRef.value.splice(newIndex, 0, doc.data())
        }
        else if (type === 'modified') {
          multiRef.value.splice(oldIndex, 1)
          multiRef.value.splice(newIndex, 0, doc.data())
        }
        else if (type === 'removed') {
          multiRef.value.splice(oldIndex, 1)
        }
      })
    }, onErrorHandler)
  }
}

但在我尝试手动完成此操作之前,我想知道是否有一种更简单的方法来反应性地组合查询“页面” - 考虑到由于 SDK 的限制我只需要拆分页面。

javascript firebase google-cloud-firestore vuejs3 vuefire
1个回答
0
投票

我在“Vue 级别”而不是在 Firestore 或 Vuefire 中解决了这个问题,在意识到这是需要反应式聚合数组的一般情况(即从几个其他数组展平的数组,这些数组都是反应式引用)并且可以是使用

computed
属性完成。

唯一的复杂之处是,在这种情况下,从我的 Firebase 函数返回的向量距离不存在于通过后来的直接 Firestore 查询获取的集合中 - 这需要哈希/查找,并且在附加时还需要在

computed
函数内进行排序块(30 个 ID)。

总而言之,流程是(简化的):

// Call the Function which does the vector search back-end
const similar = await getSimilar(docId, k)

// make lookup table of distances, and list of IDs
similar.forEach(s => {
      ids.push(s.id)
      distances.push({id:s.id, distance:s.distance})
    })

// re-fetch the docs locally to get several VueFire collections (of max 30 IDs)
const queries = await queryDocumentsByIds(collectionPath, ids)
// make all queries into Vuefire reactive collections
chunks.value = queries.map(q => useCollection(q))

// compute finalList - the reactive Firestore collection of all chunks
// add the vector distance back in from the map
const finalList = computed(() => {
  return chunks.value.reduce((refs, ref) => {
    ref.value.forEach(r => {
      const distmap = distances.find(d => d.id === r.id)
      refs.push({ ...r, distance: distmap?.distance })
    })
    return refs
  }, [])
    .sort((r1, r2) => r1.distance - r2.distance)
})

// I can now use `finalList` as a single array in my Vue paginated table 
// and it receives Firestore updates as it if was from a single Vuefire query.

这并不像复制 Vuefire 绑定那么复杂。

© www.soinside.com 2019 - 2024. All rights reserved.