1fs 中文文档教程
[![codecov](https://codecov.io/gh/samuelgozi/firebase-firestore-lite/branch/master/graph/badge.svg)](https://codecov.io/gh/samuelgozi/firebase- firestore-lite) ![bundlephobia](https://badgen.net/bundlephobia/minzip/firebase-auth-lite)
该项目的目标是为官方 Firestore JS SDK 提供一个替代库。 官方库的问题是它太重了(92kb 在撰写本文时 ), 如果您还包括 Auth 库和 firebase/app
(您必须这样做),那么它可以 加起来几百 千字节,没有任何应用程序逻辑。
我们的替代 SDK 的性能平均比官方的高 13 倍,体积小 27 倍。
What am I giving up by using this?
没有实时支持(还),也没有开箱即用的离线支持。 您还应该自己为目标浏览器转译和填充代码。 我不尝试支持旧的浏览器(嗯……IE),但这是可能的,并且是由一些社区完成的。
* Realtime 已计划,但由于缺少有关 API 工作原理的文档,因此需要一些时间。
* 离线支持将在未来提供,但可能作为第三方添加,目前不是高优先级。
API Reference
您可以在此处访问完整的 API 参考:https://samuelgozi.github.io/firebase-firestore-lite/
Getting started
NPM
使用 NPM/Yarn 安装此包:
npm install firebase-firestore-lite
# or
yarn add firebase-firestore-lite
Create a Database instance
可以使用身份验证,但不是必需的。 首先,我将向您展示如何在没有它的情况下使用它:
import { Database } from 'firebase-firestore-lite';
// All you need is the projectId. It can be found on the firebase console and in the firebase config.
const db = new Database({ projectId: 'sandbox' });
现在您可以开始使用数据库了。
大多数应用程序都应用某种基于用户的限制。 如果您想以经过身份验证的用户身份访问数据库,可以使用 "firebase-auth-lite"图书馆。 方法如下:
import { Database } from 'firebase-firestore-lite';
import Auth from 'firebase-auth-lite';
// Please read the docs on Auth library for further instructions on all the Auth features.
const auth = new Auth({
apiKey: '[The Firebase API key]'
});
// Now pass the auth instance as well as the projectId.
const db = new Database({ projectId: 'sandbox', auth });
Firestore 实例现在将使用经过身份验证的用户凭据发出所有请求。
Working with references
引用指向数据库中的特定文档或集合。 引用实际上对数据本身一无所知,事实上,您可以并且将会对甚至不存在的文档使用引用。
Reference 只是一个辅助类,它封装了一些有用的方法,旨在为我们(开发人员)节省一些时间。 但本质上,它们只是对路径的奇特抽象。
让我们创建一个:
// Reference to a collection
const usersCollection = db.ref('users');
// Reference to a document
const samuel = db.ref('users/samuel');
usersCollection
指向一个集合。 我们知道它的方式是因为路径。 路径是 users
我们知道数据库的根只有集合。 因此,它与编写 /users
相同。
samuel
指向一个文档,因为我们用来创建它的路径是一个文档的路径。
我们还可以创建对数据库根目录的引用:
const root = db.ref('/');
// Or
const root = db.ref('');
Reference's props and methods
引用具有一些有用的实例方法和属性。 请在 API 参考 中阅读更多相关信息。
Add and manage data
有多种方法可以在 Firestore 中操作数据:
- Directly getting, adding, updating or deleting by using a
Reference
. - Getting all documents within a collection.
batchGet
to retrieve a list of documents.Transaction
to either batch write or read and write at the same time.
Get a document
您可以使用 get
从数据库中获取单个文档。
const ref = db.ref('users/samuel');
const doc = await ref.get(); // Returns an instance of Document
Get all the documents in a collection
您可以使用 list
获取集合中的所有文档。 请注意,此请求在带宽和写入方面非常昂贵。
const ref = db.ref('users');
const doc = await ref.list(); // Returns an instance of List
Add a document
此方法只能通过集合引用访问。 它将创建一个具有随机生成名称的文档。
// Create a reference to the collection to which the new document will be added
const ref = db.ref('users');
// Creates the new document with the provided data, and if successful it will return a Reference it.
const newRef = await ref.add({
email: 'samuel@example.com'
});
console.log(newRef.id); // q9YUI8CQWa1KEYgZTK6t
Set a document
Set 可用于创建/更新具有已知 ID 的文档。
如果文档不存在,将创建它。 如果文档确实存在,其内容将被新提供的数据覆盖。 如果您想改为合并数据,请使用“更新”(如下)方法。
const ref = db.ref('users/samuel');
await ref.set({
email: 'samuel@example.com'
});
Update a document
update
方法会将传递给它的数据与数据库中文档的数据合并,如果文档不存在,写入将失败。
await ref.update({
profession: 'web-dev'
});
Delete a document
这将从数据库中删除文档。
ref.delete(); // Returns a promise that resolves if deleted successfully.
就这样,没了。
Batch reads
使用 db.batchGet
方法可以通过一个请求获取多个文档。
batchGet
方法可以接收(文档的)引用数组,或者如果您愿意,可以只传递路径。
// Using an array of references:
const doc1 = db.ref('col/doc1');
const doc2 = db.ref('col/doc2');
const doc3 = db.ref('col/doc3');
const docs = await db.batchGet([doc1, doc2, doc3]);
const sameDocs = await db.batchGet(['col/doc1', 'col/doc2', 'col/doc3']);
如您所见,使用文档路径而不是引用更简洁,而且性能也稍好一些。
此方法将返回一组 Document
实例。
Transactions and batch writes
事务允许我们执行批量读取,或读取和写入。 作为事务的一部分完成的所有操作都是原子的; 要么全部成功,要么都不应用。
它们受 Firestore 的配额和限制 的约束,因此请务必阅读它们。
让我们从批量写入开始。 首先,我们创建一个交易:
const tx = db.transaction();
现在 tx
持有一个 Transaction
实例。 该实例有四种方法可以帮助我们描述操作:
add
Add a document with a randomly generated id to a collection.set
Add or overwrite a document.update
Update (merge) data of an existing document.delete
Delete a document.
这些方法还没有发出任何网络请求。 它们只是帮助描述作为此事务的一部分要完成的操作。 为了提交这些更改,我们使用 commit
方法。 现在让我们描述事务,然后提交它:
// Add a new document with a random id
tx.add('users', { name: 'random', email: 'random@example.com' });
// Create a new document or overwrite an existing one.
tx.set('users/samuel', { name: 'samuel', email: 'samuel@example.com' });
// Update an existing document.
tx.update('users/daniel', { email: 'newEmail@example.com' });
// Delete a document.
tx.delete('users/george');
// Now let's commit them. This one is asynchronous and does
// indeed make the request.
try {
await tx.commit();
} catch (e) {
// Handle the failed transaction.
}
Read and write in a transaction
事务非常强大,因为您可以使用它来执行依赖于文档当前数据的操作。 有时有必要保证我们使用的是最新数据。 在事务中使用读取可以帮助我们实现这一点。
引入 get
方法。 它的工作方式与 batchGet
完全相同,这使得它不同于其他 Transaction
方法,因为它是异步的。
在事务中使用 get
方法时,我们确保对从它返回的文档的任何写操作都是原子的。 因此,如果该文档中的数据同时更改,而我们拥有的数据不再是最新的,则整个事务将失败。
以下是您可以如何使用它:
const tx = db.transaction();
const [doc1, doc2, doc3] = await tx.get(['col/doc1', 'col/doc2', 'col/doc3']);
// Work and change the data.
tx.set('col/doc1', doc1);
tx.update('col/doc2', doc2);
tx.delete('col/doc3', doc3);
// Lastly, commit.
await commit();
如果您最终使用从 tx.get
方法,那么就不用传文件路径了 在描述操作时一次又一次。 你可以省略路径 而只是传递 Document 实例:
// Instead of
tx.set('col/doc1', doc1);
tx.update('col/doc2', doc2);
tx.delete('col/doc3', doc3);
// Do
tx.set(doc1);
tx.update(doc2);
tx.delete(doc3);
但是请记住,只有当您传递从 tx.get()
方法返回的相同文档实例时,这才有效。 如果最终生成一个新对象,则必须将路径传递给它。
The runTransaction
method
有一种更简洁的方式来进行写入事务,并且还可以帮助您在失败时重试事务。
db.runTransaction()
方法可以帮助您保持整洁。 它接收一个函数作为第一个参数,尝试次数作为第二个参数(默认为 5)。
如果事务因数据更改而失败,它将为您提交并重试该功能,但如果代码由于任何其他原因而失败,它将立即抛出。
以下是您可以如何使用它:
function updateFunction(tx) {
const [doc1, doc2, doc3] = tx.get([ ... ]);
// Manipulate the data.
doc1.title = 'new title';
// ...
tx.update('col/doc1', doc1);
}
// Will resolve if and when the transaction is done.
// Will try up to 10 times.
await db.runTransaction(updateFunction, 10);
Queries
查询是通过使用引用实例的 query
方法完成的。 查询将搜索文档/集合的子项。
让我们看一个示例:
const users = db.ref('users');
const usersQuery = users.query({
where: [['age', '=>', 21]], // Array of query operations.
orderBy: 'age', // Can also be an object { field: 'age', direction: 'asc'|'desc' }
limit: 10 // The max results
});
// The above will be identical to
const usersQuery = users
.query()
.where('age', '=>', 21)
.orderBy('age')
.limit(10);
users.query()
方法可选择地接受一个选项对象,然后返回一个新的 Query 实例。 所有选项也可以使用查询方法设置,它们也可以链接起来(如第二个示例所示)。 然后你可以 run()
查询:
const results = await usersQuery.run(); // Will return the query results.
所有的查询选项都可以在 查询的 API 参考,这里是对重要部分的快速回顾:
select
Array of field paths to be returned, when empty will return the whole document.where
Comparative operations for filtering the query.from
Set by default for your current collection of the reference.orderBy
The field and direction to order by.startAt
A Document instance to start the query from.endAt
A Document instance at which to end the query.offset
Number of results to skiplimit
The maximum number of documents to return.
Collection Group Queries
有时您想要查询具有特定名称的所有集合。 例如,假设您有一个名为 users
的集合,其中包含您应用的用户。 该集合中的每个用户文档都有一个名为 posts
的子集合,它包含用户发布的所有帖子。
如果我想创建一个搜索所有帖子的查询,而不管它是由哪个用户创建的,该怎么办? 那么,为了做到这一点,您需要查询一个“集合组”,这是从 Database
实例完成的:
const query = db.collectionGroup('posts');
这将返回一个查询,该查询将对所有名为 的集合执行帖子。
但是如果我们想缩小查询的范围呢? 例如,假设我们有一个 sections
集合, 文档 电影
和歌曲
,代表不同类型的内容。 他们每个人都有一个 posts
集合,其中包含 post
文档,并且每个都有一个 comments
集合,其中包含每个 post 的评论。
如果我们只想查询 comments
集合,它们是 songs
document 的子集合,我们可以使用 的选项之一来实现code>collectionGroup
方法:
const songsDoc = db.ref('sections/songs');
const query = db.collectionGroup('comments', { parent: songsDoc });
返回的查询将不包括作为 movies
文档子级的 comments
集合的文档。 注意:父级必须是文档,您需要相应地组织数据。
Firestore emulator
为了将库配置为与 Firestore 模拟器一起使用,我们需要在创建数据库实例时更改两个设置。
host
set it tolocalhost:8080
to work with the official emulator.ssl
set to false.
这就是它的样子:
const db = new Database({
projectId: 'sandbox',
host: 'localhost:8080',
ssl: false
});
Contributing
我非常欢迎任何贡献。 语法问题、文档、示例、功能请求和代码。 但是请先打开一个问题,这样你就不会做别人做的任何事情。
[![codecov](https://codecov.io/gh/samuelgozi/firebase-firestore-lite/branch/master/graph/badge.svg)](https://codecov.io/gh/samuelgozi/firebase-firestore-lite) ![bundlephobia](https://badgen.net/bundlephobia/minzip/firebase-auth-lite)
This project goal is to provide an alternative library to the official Firestore JS SDK. The problem with the official library is that it is too heavy (92kb at the time of writing), and if you include the Auth library as well, and firebase/app
(which you have to), then it could add up to hundreds of kilobytes without any app logic.
What am I giving up by using this?
No realtime support (yet) and no out of the box offline support. You should also transpile and polyfill the code yourself for your target browsers. I don't try to support old browsers (ehm… IE), but it is possible and was done by some of the community.
* Realtime is planned, but will take some time because of lack of documentation on how the API works.
* Offline support will be available in the future, but probably as a third-party addition, and is currently not a high priority.
API Reference
You can access the full API Reference here: https://samuelgozi.github.io/firebase-firestore-lite/
Getting started
NPM
Install this package with NPM/Yarn:
npm install firebase-firestore-lite
# or
yarn add firebase-firestore-lite
Create a Database instance
It is possible to use authentication but not necessary. First I'll show you how to use it without it:
import { Database } from 'firebase-firestore-lite';
// All you need is the projectId. It can be found on the firebase console and in the firebase config.
const db = new Database({ projectId: 'sandbox' });
Now you can start working with the database.
Most apps apply some kind of user-based restrictions. If you want to access the database as an authenticated user it can be done with the "firebase-auth-lite" library. Here's how:
import { Database } from 'firebase-firestore-lite';
import Auth from 'firebase-auth-lite';
// Please read the docs on Auth library for further instructions on all the Auth features.
const auth = new Auth({
apiKey: '[The Firebase API key]'
});
// Now pass the auth instance as well as the projectId.
const db = new Database({ projectId: 'sandbox', auth });
The firestore instance will now make all the requests with the authenticates user's credentials.
Working with references
References point to specific documents or collections in a database. A reference doesn't really know anything about the data itself, in fact, you can and will use references with documents that don't even exist.
A Reference is just a helper class that encapsulates some helpful methods that are designed to save us (the devs) some time. But in their essence, they are just a fancy abstraction over paths.
Let's create one:
// Reference to a collection
const usersCollection = db.ref('users');
// Reference to a document
const samuel = db.ref('users/samuel');
usersCollection
points to a collection. The way we know it is because of the path. The path is users
and we know that the root of the database only has collections. So, it is the same as writing /users
.
samuel
points to a document because the path we used to create it was a path to a document.
We can also create a reference to the root of the database:
const root = db.ref('/');
// Or
const root = db.ref('');
Reference's props and methods
A reference has some helpful instance methods and properties. Please read more about them in the API Reference.
Add and manage data
There are multiple ways to manipulate data in Firestore:
- Directly getting, adding, updating or deleting by using a
Reference
. - Getting all documents within a collection.
batchGet
to retrieve a list of documents.Transaction
to either batch write or read and write at the same time.
Get a document
You can use get
to fetch a single document from the database.
const ref = db.ref('users/samuel');
const doc = await ref.get(); // Returns an instance of Document
Get all the documents in a collection
You can use list
to fetch a all the documents in a collection. Be mindful that this request is expensive in terms of bandwidth and writes.
const ref = db.ref('users');
const doc = await ref.list(); // Returns an instance of List
Add a document
This method is only accessible through collection references. It will create a document with a randomly generated name.
// Create a reference to the collection to which the new document will be added
const ref = db.ref('users');
// Creates the new document with the provided data, and if successful it will return a Reference it.
const newRef = await ref.add({
email: 'samuel@example.com'
});
console.log(newRef.id); // q9YUI8CQWa1KEYgZTK6t
Set a document
Set can be used to create/update a document with a known ID.
If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data. If you want to merge the data instead, use the "update" (below) method.
const ref = db.ref('users/samuel');
await ref.set({
email: 'samuel@example.com'
});
Update a document
The update
method will merge the data passed to it with the data of the document in the database and the write will fail if the document doesn't exist.
await ref.update({
profession: 'web-dev'
});
Delete a document
This will delete the document from the database.
ref.delete(); // Returns a promise that resolves if deleted successfully.
Just like that, it's gone.
Batch reads
It is possible to get multiple documents with one request by using the db.batchGet
method.
The batchGet
method can receives an array of References (of documents) or if you prefer you can just pass the paths.
// Using an array of references:
const doc1 = db.ref('col/doc1');
const doc2 = db.ref('col/doc2');
const doc3 = db.ref('col/doc3');
const docs = await db.batchGet([doc1, doc2, doc3]);
const sameDocs = await db.batchGet(['col/doc1', 'col/doc2', 'col/doc3']);
As you can see, using document paths instead of references is much cleaner, and it also performs a tiny bit better.
This method will return an array of Document
instances.
Transactions and batch writes
Transactions allow us to perform batch reads, or reads and writes. All of the operations done as a part of a transaction are atomic; Either all of them succeed, or none of them are applied.
They are subject to the Quotas and Limits of Firestore, so make sure you read them.
Let's start with a batch write. First, we create a transaction:
const tx = db.transaction();
Now tx
holds a Transaction
instance. The instance has four methods that help us describe operations:
add
Add a document with a randomly generated id to a collection.set
Add or overwrite a document.update
Update (merge) data of an existing document.delete
Delete a document.
These methods do not make any network requests yet. They are just helpers to describe the operations to be done as part of this transaction. In order to commit those changes, we use the commit
method. Now let's describe the transaction, and then commit it:
// Add a new document with a random id
tx.add('users', { name: 'random', email: 'random@example.com' });
// Create a new document or overwrite an existing one.
tx.set('users/samuel', { name: 'samuel', email: 'samuel@example.com' });
// Update an existing document.
tx.update('users/daniel', { email: 'newEmail@example.com' });
// Delete a document.
tx.delete('users/george');
// Now let's commit them. This one is asynchronous and does
// indeed make the request.
try {
await tx.commit();
} catch (e) {
// Handle the failed transaction.
}
Read and write in a transaction
A transaction is very powerful because you can use it to perform operations that depend on the current data of a document. Sometimes it is necessary to have a guarantee that we are working with the latest data. Using reads within a transaction can help us accomplish that.
Introducing the get
method. It works exactly as a batchGet
, and that makes it different than the other Transaction
methods because it is asynchronous.
When using the get
method inside a transaction we make sure that any write operation on a document returned from it will be atomic. So, if the data in that document changed concurrently, and the one we have is no longer up-to-date, the whole transaction will fail.
Here is how you can use it:
const tx = db.transaction();
const [doc1, doc2, doc3] = await tx.get(['col/doc1', 'col/doc2', 'col/doc3']);
// Work and change the data.
tx.set('col/doc1', doc1);
tx.update('col/doc2', doc2);
tx.delete('col/doc3', doc3);
// Lastly, commit.
await commit();
If you end up using the same Document instances returned from the tx.get
method, then there is no need to pass the document path again and again when describing an operations. You can omit the path and just pass the Document instance instead:
// Instead of
tx.set('col/doc1', doc1);
tx.update('col/doc2', doc2);
tx.delete('col/doc3', doc3);
// Do
tx.set(doc1);
tx.update(doc2);
tx.delete(doc3);
But remember, this works only if you pass the same document instance returned from the tx.get()
method. If you end up generating a new object, then you have to pass the path to it.
The runTransaction
method
There is a cleaner way to make a transaction with writes, and can also help you retry the transaction when failed.
The db.runTransaction()
method can help you keep things cleaner. It receives a function as its first argument, and the number of attempts as the second argument (defaults to 5).
It will commit and retry the function for you if the transaction fails because the data changed, but it will throw immediately if the code failed due to any other reason.
Here is how you can use it:
function updateFunction(tx) {
const [doc1, doc2, doc3] = tx.get([ ... ]);
// Manipulate the data.
doc1.title = 'new title';
// ...
tx.update('col/doc1', doc1);
}
// Will resolve if and when the transaction is done.
// Will try up to 10 times.
await db.runTransaction(updateFunction, 10);
Queries
Queries are done by using the query
method of a reference instance. The query will search through the children of document/collection.
let's look at an example:
const users = db.ref('users');
const usersQuery = users.query({
where: [['age', '=>', 21]], // Array of query operations.
orderBy: 'age', // Can also be an object { field: 'age', direction: 'asc'|'desc' }
limit: 10 // The max results
});
// The above will be identical to
const usersQuery = users
.query()
.where('age', '=>', 21)
.orderBy('age')
.limit(10);
The users.query()
method optionally accepts an options object, and then returns a new Query instance. All the options can also be set by using the query methods, and they can also be chained (as seen in the second example). You can then run()
the query:
const results = await usersQuery.run(); // Will return the query results.
All the query options can be seen in the API reference for Query, bet here is a quick recap of the important ones:
select
Array of field paths to be returned, when empty will return the whole document.where
Comparative operations for filtering the query.from
Set by default for your current collection of the reference.orderBy
The field and direction to order by.startAt
A Document instance to start the query from.endAt
A Document instance at which to end the query.offset
Number of results to skiplimit
The maximum number of documents to return.
Collection Group Queries
Sometimes you want to query all collections with a certain name. Lets say for example that you have a collection called users
which contains the users of your app. Each user document inside of that collection has a child collection called posts
, at it contains all of the posts that user has published.
What if I want to make a query that will search all the posts regardless of which user created it? Well, in order to do that you will want to query a "Collection Group", and this is done from the Database
instance:
const query = db.collectionGroup('posts');
This will return a query that will be performed on all collections called posts
.
But what if we want to narrow the scope of the query? For example, lets say that we have a sections
collection, with the documents movies
and songs
, which represent different type of content. Each one of them has a posts
collection with post
documents, and each of these has a comments
collection with the comments on each post
.
If we only want to query the comments
collection that are children of the songs
document we can do that by using one of the options of the collectionGroup
method:
const songsDoc = db.ref('sections/songs');
const query = db.collectionGroup('comments', { parent: songsDoc });
The returned query will not include documents of the comments
collection that are children of the movies
document. NOTE: The parent has to be a document, you will need to organize your data accordingly.
Firestore emulator
In order to configure the library to work with the Firestore emulator we need to change two settings when creating the Database instance.
host
set it tolocalhost:8080
to work with the official emulator.ssl
set to false.
This is how it would look:
const db = new Database({
projectId: 'sandbox',
host: 'localhost:8080',
ssl: false
});
Contributing
I very much welcome any contribution. Grammar issues, docs, examples, features requests, and code. But please open an issue before so that you don't work on anything that someone else is.