实时数据
信息
如果您正在寻找 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()
<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()
等可组合函数来实现这一点
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
可以是getter、computed()
或 ref()
。如果您使用的是 ref()
,请确保使用 shallowRef()
而不是 ref()
,以获得更好的性能。
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
作为数据源的值。当您希望在组件生命周期的后期开始观察文档或集合,或者无法计算出有效的文档路径时(例如,当用户未登录时),这很有用
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
)
订阅状态
所有可组合函数还可以解构以访问其他有用的数据,例如初始加载是否仍在挂起?或订阅是否失败?。您只需要解构可组合函数返回的值
// 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
选项,这将自动销毁订阅,只要文档或集合完全获取即可
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/键。它被设置为不可枚举属性,因此在使用扩展运算符时不会被复制。
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
等方法并访问其属性 latitude
和 longitude
。
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 按原样存储,这意味着您可以直接使用 isEqual
、toDate
等方法并访问其属性 seconds
和 nanoseconds
。
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
{
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
标识的文档
{
content: '...',
sharedWith: [
doc(collection(db, 'users'), '2'),
doc(collection(db, 'users'), '3'),
]
}
sharedWith
也是一个引用数组,但这些引用是用户。用户还包含指向文档的引用,因此,如果我们自动绑定每个嵌套引用,我们最终可能会得到一个无限内存消耗的绑定。默认情况下,如果我们使用 VueFire 绑定 users/1
,我们将得到以下结果
{
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
选项 来自定义此行为
// override the default value of 2 for maxRefDepth
useDocument(doc(db, 'users/1'), { maxRefDepth: 1 })
原始值(仅限数据库)
在实时数据库中,您可以推送原始值,例如字符串、数字、布尔值等。当对包含原始值的数据库引用调用 useDatabaseList()
时,您将获得一个略有不同的值。您将获得一个对象数组,而不是一个值数组,其中包含 $value
和 id
属性。这是因为 VueFire 需要跟踪每个值的键,以便添加、更新或删除它们。
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
要强制执行类型,您只需要在使用不同的可组合函数时传递泛型类型
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()
选项。
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()
接受两个泛型,而不是一个
// ...
import type { DocumentData } from 'firebase/firestore'
const todoList = useDocument(
doc(db, 'todos').withConverter<TodoI, DocumentData>({
// ...
})
)
警告
虽然您可以在 withConverter()
中返回几乎任何东西,但如果您使用的是SSR,请确保您的对象是可序列化的。例如,您不能返回自定义类或函数。