跳至内容

实时数据

信息

如果您正在寻找 VueFire Options API 指南,请务必也查看专用页面。此页面仍然包含比 Options API 页面更多信息,Options API 页面更侧重于其语法。

在 VueFire 中,对数据更改的订阅是透明处理的。这就是为什么我们总是谈论绑定:您只需要提供数据源(集合、查询或文档),VueFire 会处理剩下的!

使用 Firebase Database 和 Firestore 时,您可以选择一次性检索数据或订阅更改,方法是使用 onSnapshot()onValue() 等方法。VueFire 会自动为您处理订阅,并在数据更改时更新数据,方法是在内部使用这些函数,从而大大简化将 Vue 数据连接到 Firebase 实时数据的整个过程。它公开了一些可组合函数 来创建这些实时绑定,需要注意的是,与其他可组合函数一样,这些函数旨在在 setup() 函数或 <script setup> 中使用。您仍然可以在这些上下文之外使用它们来处理高级场景,例如 Vuex/Pinia 或全局绑定,但我们将重点介绍这里最常见的用例。您也可以使用Options API 等效项,在本节文档中,我们将重点介绍 Composition API 版本,并为您提供 Options API 的等效项。

声明式实时数据

使用 useCollection()useDatabaseList()useDocument()useDatabaseObject() 可组合函数来创建与 Firestore 和/或实时数据库连接的实时数据。这些函数接受指向集合、查询或文档的源引用。它们还接受这些引用的响应式版本,例如 ref()computed()

vue
<script setup>
import { useCollection } from 'vuefire'
import { collection } from 'firebase/firestore'

const todos = useCollection(collection(db, 'todos'))
const someTodo = useDocument(doc(collection(db, 'todos'), 'someId'))
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <span>{{ todo.text }}</span>
    </li>
  </ul>
</template>

这些可组合函数中的每一个都返回一个包含数据的 Vue Ref。请注意,这是一个只读数据,您不应该直接修改它,而应该使用 Firebase SDK。VueFire 会自动将数据与数据库同步。

有时,您需要开始观察不同的文档或集合,假设您有一个联系人集合,并且您根据 URL 显示特定联系人,例如在 /contacts/24 上显示 ID 等于 24 的联系人,您可以通过将数据源的响应式变量传递给 useDocument()useDatabaseObject() 等可组合函数来实现这一点

ts
const route = useRoute()
// since route is reactive, `contactSource` will be reactive too
const contactSource = computed(() =>
  doc(collection(db, 'contacts'), route.params.id)
)
// contact will always be in sync with the data source
const contact = useDocument(contactSource)

这样,当路由更改时,文档将更新为新文档,自动取消订阅先前文档并订阅新文档。

提示

contactSource 可以是gettercomputed()ref()。如果您使用的是 ref(),请确保使用 shallowRef() 而不是 ref(),以获得更好的性能。

ts
const asRef = shallowRef(dbRef(db, 'contacts/' + route.params.id))
useDocument(asRef)
const asComputed = computed(() => dbRef(db, 'contacts/' + route.params.id))
useDocument(asComputed)
// a getter is the lightest option
useDocument(() => dbRef(db, 'contacts/' + route.params.id))

最重要的是,VueFire 还允许将 null 作为数据源的值。当您希望在组件生命周期的后期开始观察文档或集合,或者无法计算出有效的文档路径时(例如,当用户未登录时),这很有用

ts
const user = useCurrentUser()
const myContactList = useCollection(() =>
  user.value
    ? // Firebase will error if a null value is passed to `collection()`
      collection(db, 'users', user.value.id, 'contacts')
    : // this will be considered as no data source
      null
)

订阅状态

所有可组合函数还可以解构以访问其他有用的数据,例如初始加载是否仍在挂起?订阅是否失败?。您只需要解构可组合函数返回的值

ts
// instead of writing
const contact = useDocument(contactSource)
// write
const {
  // rename the Ref to something more meaningful
  data: contact,
  // is the subscription still pending?
  pending,
  // did the subscription fail?
  error,
  // A promise that resolves or rejects when the initial state is loaded
  promise,
} = useDocument(contactSource)

请注意,我们将 data 重命名为在上下文中更有意义的名称。

警告

可以在 Ref 上定义的所有属性都定义为不可枚举属性,这意味着在使用扩展运算符时,它们不会被复制,例如 const { data, ...rest } = useDocument(contactSource)。这样做是为了确保它们完全被忽略,并且不会在其他地方(例如开发工具)中造成问题。

一次性获取数据

一次获取数据,请传递once 选项,这将自动销毁订阅,只要文档或集合完全获取即可

ts
import { useCollection, useDocument } from 'vuefire'
import { collection, doc } from 'firebase/firestore'

const todos = useCollection(collection(db, 'todos'), {
  once: true,
})
const someTodo = useDocument(doc(collection(db, 'todos'), 'someId'), {
  once: true,
})

VueFire 添加内容

VueFire 向数据快照添加了一些属性,以使其更易于使用。

文档的 id

每个文档/对象都有一个方便的 id 属性,它是文档/对象的 ID/键。它被设置为不可枚举属性,因此在使用扩展运算符时不会被复制。

js
this.user.id // jORwjIykFn1NmkdzTkhU
// the id is non enumerable
Object.keys(this.user).includes('id') // false

// it originally comes from the `id` attribute
doc(collection(db, 'users'), 'jORwjIykFn1NmkdzTkhU').id // 'jORwjIykFn1NmkdzTkhU'
// More at https://firebase.google.com/docs/reference/js/firestore_.documentreference.md#documentreferenceid

此行为可以通过serialize/converter 选项 进行自定义。请注意,在这两种情况下,您必须保留 id 属性,以便 VueFire 正确工作

GeoPoints(仅限 Firestore)

在 Firestore 中,您可以存储GeoPoints。它们被 VueFire 按原样检索,这意味着您可以直接使用 isEqual 等方法并访问其属性 latitudelongitude

js
import { GeoPoint } from 'firebase/firestore'

// add Paris to the list of cities and wait for the operation
// to be finished
await addDoc(collection(db, 'cities'), {
  name: 'Paris',
  location: new GeoPoint(48.8588377, 2.2770206),
})

// somewhere else...
// we consider `cities` to be the result af `useCollection(collection(db, 'cities'))`
// we retrieve Paris that was just added
const paris = cities.value.at(-1)
paris.location.latitude // 48.8588377
paris.location.longitude // 2.2770206

时间戳(仅限 Firestore)

在 Firestore 中,您可以存储时间戳。它们被 VueFire 按原样存储,这意味着您可以直接使用 isEqualtoDate 等方法并访问其属性 secondsnanoseconds

js
import { Timestamp } from 'firebase/firestore'

// Add "La prise de la Bastille" to a list of events
// and wait for th operation to be finished
await addDoc(collection(db, 'events'), {
  name: 'Prise de la Bastille',
  date: Timestamp.fromDate(new Date('1789-07-14')),
})

// somewhere else...
// we consider `events` to be the result af `useCollection(collection(db, 'events'))`
// we retrieve the event we just added
const prise = events.value.at(-1)
prise.date.seconds // -5694969600
prise.date.nanoseconds // 0
prise.toDate() // Tue Jul 14 1789

引用(仅限 Firestore)

在 Firestore 中,您可以存储嵌套引用。您可以将其视为指向文档内文档的指针。VueFire 会自动绑定在集合和文档中找到的引用。这也适用于嵌套引用(在绑定引用中找到的引用)。默认情况下,VueFire 会停止在该级别(2 级嵌套),但您可以使用 maxRefDepth 更改它。

假设一些用户拥有被其他用户查看的文档。这可能是 users/1

js
{
  name: 'Claire',
  documents: [
    // The document is stored somewhere else. Here we are only holding a reference
    doc(collection(db, 'documents'), 'gift-list'),
  ],
}

在上面的示例中,documents 是一个引用数组。让我们看一下由 gift-list 标识的文档

js
{
  content: '...',
  sharedWith: [
    doc(collection(db, 'users'), '2'),
    doc(collection(db, 'users'), '3'),
  ]
}

sharedWith 也是一个引用数组,但这些引用是用户。用户还包含指向文档的引用,因此,如果我们自动绑定每个嵌套引用,我们最终可能会得到一个无限内存消耗的绑定。默认情况下,如果我们使用 VueFire 绑定 users/1,我们将得到以下结果

js
{
  name: 'Claire',
  documents: [
    {
      content: '...',
      sharedWith: [
        {
          name: 'Chris',
          documents: [
            'documents/chris-todo-list',
          ]
        },
        {
          name: 'Leon',
          documents: [
            'documents/leon-todo-list',
            'documents/leon-book',
          ],
        },
      ],
    },
  ],
}

documents.sharedWith.documents 最终成为字符串数组。这些字符串实际上是路径,可以传递给 doc(),例如 doc(db, 'documents/leon-book'),以获取指向文档的实际引用。通过成为字符串而不是引用,可以使用 VueFire 将绑定文档显示为纯文本。

可以通过提供maxRefDepth 选项 来自定义此行为

js
// override the default value of 2 for maxRefDepth
useDocument(doc(db, 'users/1'), { maxRefDepth: 1 })

写入数据 部分中,详细了解将引用写入数据库

原始值(仅限数据库)

在实时数据库中,您可以推送原始值,例如字符串、数字、布尔值等。当对包含原始值的数据库引用调用 useDatabaseList() 时,您将获得一个略有不同的值。您将获得一个对象数组,而不是一个值数组,其中包含 $valueid 属性。这是因为 VueFire 需要跟踪每个值的键,以便添加、更新或删除它们。

js
import { ref as databaseRef, push } from 'firebase/database'

const numbersRef = databaseRef(db, 'numbers')
// add some numbers
push(numbersRef, 24)
push(numbersRef, 7)
push(numbersRef, 10)

const numberList = useDatabaseList(numbersRef)
// [{ $value: 24, id: '...' }, { $value: 7, id: '...' }, { $value: 10, id: '...' }]
// note the order might be different

TypeScript

要强制执行类型,您只需要在使用不同的可组合函数时传递泛型类型

ts
const contacts = useCollection<Contact>(collection(db, 'contacts'))
const settings = useDocument<Settings>(
  doc(collection(db, 'settings'), 'someId')
)

请注意,这只是一个类型注释,它不会执行任何运行时验证。如果您希望进行运行时验证,可以使用 withConverter() 方法,如下所示。

Firestore .withConverter()

推荐的 Firebase 方法是为 Firestore 使用 withConverter()

信息

.withConverter() 是 Firestore 的一项功能,在数据库中没有等效项,但您可以改用 VueFire 的serialize() 选项

ts
import { firestoreDefaultConverter } from 'vuefire'

interface TodoI {
  text: string
  finished: boolean
}

const todoList = useDocument(
  doc(db, 'todos').withConverter<TodoI>({
    fromFirestore: (snapshot) => {
      const data = firestoreDefaultConverter.fromFirestore(snapshot)
      // usually you can do data validation here
      if (!data || !isValidTodoItem(data)) return null

      return data
    },
    toFirestore: firestoreDefaultConverter.toFirestore,
  })
)

在 Firebase 10 中,withConverter() 接受两个泛型,而不是一个

ts
// ...
import type { DocumentData } from 'firebase/firestore'

const todoList = useDocument(
  doc(db, 'todos').withConverter<TodoI, DocumentData>({
    // ...
  })
)

警告

虽然您可以在 withConverter() 中返回几乎任何东西,但如果您使用的是SSR,请确保您的对象是可序列化的。例如,您不能返回自定义类或函数。