Firebase調査
ドキュメントの設定
import { doc, setDoc } from "firebase/firestore";
// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
マージの挙動
import { doc, setDoc } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
type User = {
displayName: string
photoURL: string
createdAt: firebase.firestore.Timestamp
updatedAt: firebase.firestore.Timestamp
}
ドキュメントの作成
旧
type User = {
displayName: string
photoURL: string
createdAt: firebase.firestore.Timestamp
updatedAt: firebase.firestore.Timestamp
}
async createdUser(user: User): Promise<DocumentReference> {
return firebase.firestore().collection('users').add(user)
}
ドキュメントリファレンスの取得
db
はfirebase.firestore
だと思って大丈夫。
旧
const alovelaceDocumentRef = db.collection('users').doc('alovelace');
新
import { doc } from "firebase/firestore";
const alovelaceDocumentRef = doc(db, 'users', 'alovelace');
firebase/firestore
からdoc
をインポートし、そのdoc
を使用することでドキュメントリファレンスを作成する。docは第一引数にdb
、第二引数にコレクション名
、第三引数にドキュメント名
を指定します。
docの第三引数を空っぽにすると、ドキュメントIDが自動採番されるのかな?
コレクションリファレンスの取得。
旧
const usersCollectionRef = db.collection('users')
新
import { collection } from "firebase/firestore";
const usersCollectionRef = collection(db, 'users');
コレクションリファレンスを作成するときは、firebase/firestore
からcollection
をインポートして使用する。collection
の第一引数にdb
を指定し、第二引数にコレクション名を指定する。
ドキュメントリファレンスの取得の省略形
旧
const alovelaceDocumentRef = db.doc('users/alovelace');
新
import { doc } from "firebase/firestore";
const alovelaceDocumentRef = doc(db, 'users/alovelace');
ドキュメントリファレンスを、省略して取得することも可能。doc
の第二引数にコレクション名/ドキュメントID
を指定することで取得可能。
サブコレクションのドキュメントリファレンスの取得
旧
const messageRef = db.collection('rooms').doc('roomA')
.collection('messages').doc('message1');
新
import { doc } from "firebase/firestore";
const messageRef = doc(db, "rooms", "roomA", "messages", "message1");
doc
は第二引数から、コレクション→ドキュメント→コレクション→ドキュメントの繰り返し。
cities
コレクションの中に、LA
というドキュメントIDのドキュメントを作成し、ドキュメントの作成が完了すれば、コンソールにメッセージを表示する。
旧
// Add a new document in collection "cities"
db.collection("cities").doc("LA").set({
name: "Los Angeles",
state: "CA",
country: "USA"
})
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
新
import { doc, setDoc } from "firebase/firestore";
// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
console.log("Document successfully written!");
doc
はドキュメントリファレンスを取得するだけ。そのドキュメントリファレンスを用いてドキュメントの生成を行うのはsetDoc
を使用する。setDoc
は第一引数にドキュメントリファレンスを持ち、第二引数にデータそのものを記述する。この第二引数は、旧バージョンの.set
の引数と一致する。
ドキュメントリファレンスに.set
を用いて、データを更新する方法。marge : true
を使用する。
基本的には、この方法を使用するよりもupdate
を使用した方が良い(createとupdateのセキュリティルールが同一になるため、ドキュメントの作成時刻を持つデータモデルを扱う場合に安全性を確保できないため。)
データが存在する場合はupdate、存在しなければcreateというユースケースで利用する場合はあるかも。
旧
const cityRef = db.collection('cities').doc('BJ');
const setWithMerge = cityRef.set({
capital: true
}, { merge: true });
新
import { doc, setDoc } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
setDoc
の第三引数にmerge : true
を与える。
データ型
Firestoreでは、文字列・ブール値・日付・NULL・ネストされた文字列・オブジェクトなどのデータ型をオブジェクトに書き込むことができる。Cloud Firestore
は、数値を常にdouble
型として保存する。
旧
var docData = {
stringExample: "Hello world!",
booleanExample: true,
numberExample: 3.14159265,
dateExample: firebase.firestore.Timestamp.fromDate(new Date("December 10, 1815")),
arrayExample: [5, true, "hello"],
nullExample: null,
objectExample: {
a: 5,
b: {
nested: "foo"
}
}
};
db.collection("data").doc("one").set(docData).then(() => {
console.log("Document successfully written!");
});
新
import { doc, setDoc, Timestamp } from "firebase/firestore";
const docData = {
stringExample: "Hello world!",
booleanExample: true,
numberExample: 3.14159265,
dateExample: Timestamp.fromDate(new Date("December 10, 1815")),
arrayExample: [5, true, "hello"],
nullExample: null,
objectExample: {
a: 5,
b: {
nested: "foo"
}
}
};
await setDoc(doc(db, "data", "one"), docData);
新バージョンでは、Timestamp
はTimestamp
としてインポートする必要がある。
ドキュメントの追加
こんな風に書いてありました。
set() を使用してドキュメントを作成する場合、作成するドキュメントの ID を指定する必要があります。次に例を示します。
一応、doc
の引数を空っぽにしてset
を行うと、ドキュメントのIDを自動採番してドキュメントを作成することが可能になります。しかし、ドキュメントのIDを自動採番するなら、コレクションに対してadd
を使用した方が良いかもです。
ドキュメントIDを指定してドキュメントを作成するパターン。
旧
db.collection("cities").doc("new-city-id").set(data);
新
import { doc, setDoc } from "firebase/firestore";
await setDoc(doc(db, "cities", "new-city-id"), data);
自動採番するパターン(add)を使用。
旧
// Add a new document with a generated id.
db.collection("cities").add({
name: "Tokyo",
country: "Japan"
})
.then((docRef) => {
console.log("Document written with ID: ", docRef.id);
})
.catch((error) => {
console.error("Error adding document: ", error);
});
新
import { collection, addDoc } from "firebase/firestore";
// Add a new document with a generated id.
const docRef = await addDoc(collection(db, "cities"), {
name: "Tokyo",
country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
自動生成されたIDを持つドキュメントリファレンスを作成して、後でそのリファレンスを使用する場合。
旧
// Add a new document with a generated id.
const newCityRef = db.collection("cities").doc();
// later...
newCityRef.set(data);
新
import { collection, doc, setDoc } from "firebase/firestore";
// Add a new document with a generated id
const newCityRef = doc(collection(db, "cities"));
// later...
await setDoc(newCityRef, data);
新の書き方が結構特殊。collection
を用いてコレクションリファレンスを作成し、それをdocの引数に渡すことで自動採番されたドキュメントリファレンスを作成する。
ドキュメントの更新。
旧
var washingtonRef = db.collection("cities").doc("DC");
// Set the "capital" field of the city 'DC'
return washingtonRef.update({
capital: true
})
.then(() => {
console.log("Document successfully updated!");
})
.catch((error) => {
// The document probably doesn't exist.
console.error("Error updating document: ", error);
});
新
import { doc, updateDoc } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Set the "capital" field of the city 'DC'
await updateDoc(washingtonRef, {
capital: true
});
doc
はいつも通り、第一引数がdb
であり、第二引数以降はコレクション→ドキュメント→コレクション→ドキュメントと続いていく。指定したドキュメントリファレンスに対して、updateDoc
を使用すればOK。
サーバータイムスタンプ
旧
var docRef = db.collection('objects').doc('some-id');
// Update the timestamp field with the value from the server
var updateTimestamp = docRef.update({
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
新
import { updateDoc, serverTimestamp } from "firebase/firestore";
const docRef = doc(db, 'objects', 'some-id');
// Update the timestamp field with the value from the server
const updateTimestamp = await updateDoc(docRef, {
timestamp: serverTimestamp()
});
なにはなくともドキュメントリファレンスの取得が大事。
後は、updateDocを用いてドキュメントを更新すればOK。その際、FieldValueのserverTimestampではなく、serverTimestamp
を使用すればOK。
ネストされたオブジェクトのフィールドを更新する。
旧
// Create an initial document to update.
var frankDocRef = db.collection("users").doc("frank");
frankDocRef.set({
name: "Frank",
favorites: { food: "Pizza", color: "Blue", subject: "recess" },
age: 12
});
// To update age and favorite color:
db.collection("users").doc("frank").update({
"age": 13,
"favorites.color": "Red"
})
.then(() => {
console.log("Document successfully updated!");
});
新
import { doc, setDoc, updateDoc } from "firebase/firestore";
// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
name: "Frank",
favorites: { food: "Pizza", color: "Blue", subject: "recess" },
age: 12
});
// To update age and favorite color:
await updateDoc(frankDocRef, {
"age": 13,
"favorites.color": "Red"
});
配列内の要素を更新する。
旧
var washingtonRef = db.collection("cities").doc("DC");
// Atomically add a new region to the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});
新
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayRemove("east_coast")
});
数値を増やす
旧
var washingtonRef = db.collection('cities').doc('DC');
// Atomically increment the population of the city by 50.
washingtonRef.update({
population: firebase.firestore.FieldValue.increment(50)
});
新
import { doc, updateDoc, increment } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically increment the population of the city by 50.
await updateDoc(washingtonRef, {
population: increment(50)
});
トランザクションを作成して実行
旧
// Create a reference to the SF doc.
const sfDocRef = db.collection("cities").doc("SF");
// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });
return db.runTransaction((transaction) => {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(sfDocRef).then((sfDoc) => {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
// Add one person to the city population.
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
var newPopulation = sfDoc.data().population + 1;
transaction.update(sfDocRef, { population: newPopulation });
});
}).then(() => {
console.log("Transaction successfully committed!");
}).catch((error) => {
console.log("Transaction failed: ", error);
});
新
import { runTransaction } from "firebase/firestore";
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(sfDocRef);
if (!sfDoc.exists()) {
throw "Document does not exist!";
}
const newPopulation = sfDoc.data().population + 1;
transaction.update(sfDocRef, { population: newPopulation });
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
トランザクションから情報を渡す
旧
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");
db.runTransaction((transaction) => {
return transaction.get(sfDocRef).then((sfDoc) => {
if (!sfDoc.exists) {
throw "Document does not exist!";
}
var newPopulation = sfDoc.data().population + 1;
if (newPopulation <= 1000000) {
transaction.update(sfDocRef, { population: newPopulation });
return newPopulation;
} else {
return Promise.reject("Sorry! Population is too big.");
}
});
}).then((newPopulation) => {
console.log("Population increased to ", newPopulation);
}).catch((err) => {
// This will be an "population is too big" error.
console.error(err);
});
新
import { doc, runTransaction } from "firebase/firestore";
// Create a reference to the SF doc.
const sfDocRef = doc(db, "cities", "SF");
try {
const newPopulation = await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(sfDocRef);
if (!sfDoc.exists()) {
throw "Document does not exist!";
}
const newPop = sfDoc.data().population + 1;
if (newPop <= 1000000) {
transaction.update(sfDocRef, { population: newPop });
return newPop;
} else {
return Promise.reject("Sorry! Population is too big");
}
});
console.log("Population increased to ", newPopulation);
} catch (e) {
// This will be a "population is too big" error.
console.error(e);
}
バッチ書き込み
旧
// Get a new write batch
var batch = db.batch();
// Set the value of 'NYC'
var nycRef = db.collection("cities").doc("NYC");
batch.set(nycRef, {name: "New York City"});
// Update the population of 'SF'
var sfRef = db.collection("cities").doc("SF");
batch.update(sfRef, {"population": 1000000});
// Delete the city 'LA'
var laRef = db.collection("cities").doc("LA");
batch.delete(laRef);
// Commit the batch
batch.commit().then(() => {
// ...
});
新
import { writeBatch, doc } from "firebase/firestore";
// Get a new write batch
const batch = writeBatch(db);
// Set the value of 'NYC'
const nycRef = doc(db, "cities", "NYC");
batch.set(nycRef, {name: "New York City"});
// Update the population of 'SF'
const sfRef = doc(db, "cities", "SF");
batch.update(sfRef, {"population": 1000000});
// Delete the city 'LA'
const laRef = doc(db, "cities", "LA");
batch.delete(laRef);
// Commit the batch
await batch.commit();
ドキュメントを削除する。
旧
db.collection("cities").doc("DC").delete().then(() => {
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
新
import { doc, deleteDoc } from "firebase/firestore";
await deleteDoc(doc(db, "cities", "DC"));
警告: ドキュメントを削除しても、そのドキュメントのサブコレクションは削除されません。
フィールドを削除する
旧
var cityRef = db.collection('cities').doc('BJ');
// Remove the 'capital' field from the document
var removeCapital = cityRef.update({
capital: firebase.firestore.FieldValue.delete()
});
新
import { doc, updateDoc, deleteField } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
// Remove the 'capital' field from the document
await updateDoc(cityRef, {
capital: deleteField()
});
データの例
旧
var citiesRef = db.collection("cities");
citiesRef.doc("SF").set({
name: "San Francisco", state: "CA", country: "USA",
capital: false, population: 860000,
regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
name: "Los Angeles", state: "CA", country: "USA",
capital: false, population: 3900000,
regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
name: "Washington, D.C.", state: null, country: "USA",
capital: true, population: 680000,
regions: ["east_coast"] });
citiesRef.doc("TOK").set({
name: "Tokyo", state: null, country: "Japan",
capital: true, population: 9000000,
regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
name: "Beijing", state: null, country: "China",
capital: true, population: 21500000,
regions: ["jingjinji", "hebei"] });
新
import { collection, doc, setDoc } from "firebase/firestore";
const citiesRef = collection(db, "cities");
await setDoc(doc(citiesRef, "SF"), {
name: "San Francisco", state: "CA", country: "USA",
capital: false, population: 860000,
regions: ["west_coast", "norcal"] });
await setDoc(doc(citiesRef, "LA"), {
name: "Los Angeles", state: "CA", country: "USA",
capital: false, population: 3900000,
regions: ["west_coast", "socal"] });
await setDoc(doc(citiesRef, "DC"), {
name: "Washington, D.C.", state: null, country: "USA",
capital: true, population: 680000,
regions: ["east_coast"] });
await setDoc(doc(citiesRef, "TOK"), {
name: "Tokyo", state: null, country: "Japan",
capital: true, population: 9000000,
regions: ["kanto", "honshu"] });
await setDoc(doc(citiesRef, "BJ"), {
name: "Beijing", state: null, country: "China",
capital: true, population: 21500000,
regions: ["jingjinji", "hebei"] });
``
ドキュメントを取得する
旧
var docRef = db.collection("cities").doc("SF");
docRef.get().then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
新
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
ドキュメントを取得する
旧
var docRef = db.collection("cities").doc("SF");
// Valid options for source are 'server', 'cache', or
// 'default'. See https://firebase.google.com/docs/reference/js/firebase.firestore.GetOptions
// for more information.
var getOptions = {
source: 'cache'
};
// Get a document, forcing the SDK to fetch from the offline cache.
docRef.get(getOptions).then((doc) => {
// Document was found in the cache. If no cached document exists,
// an error will be returned to the 'catch' block below.
console.log("Cached document data:", doc.data());
}).catch((error) => {
console.log("Error getting cached document:", error);
});
新
import { doc, getDocFromCache } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
// Get a document, forcing the SDK to fetch from the offline cache.
try {
const doc = await getDocFromCache(docRef);
// Document was found in the cache. If no cached document exists,
// an error will be returned to the 'catch' block below.
console.log("Cached document data:", doc.data());
} catch (e) {
console.log("Error getting cached document:", e);
}
コレクションから複数のドキュメントを取得する
旧
db.collection("cities").where("capital", "==", true)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
新
import { collection, query, where, getDocs } from "firebase/firestore";
const q = query(collection(db, "cities"), where("capital", "==", true));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
コレクションのすべてのドキュメントを取得する
旧
db.collection("cities").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
新
import { collection, getDocs } from "firebase/firestore";
const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
リアルタイムリスナー
旧
db.collection("cities").doc("SF")
.onSnapshot((doc) => {
console.log("Current data: ", doc.data());
});
新
import { doc, onSnapshot } from "firebase/firestore";
const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
console.log("Current data: ", doc.data());
});
ローカル変更のイベント
旧
db.collection("cities").doc("SF")
.onSnapshot((doc) => {
var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
console.log(source, " data: ", doc.data());
});
新
import { doc, onSnapshot } from "firebase/firestore";
const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
const source = doc.metadata.hasPendingWrites ? "Local" : "Server";
console.log(source, " data: ", doc.data());
});