Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScriptから使いやすいFirestore-simpleを紹介します(2020年版)

 TypeScriptから使いやすいFirestore-simpleを紹介します(2020年版)

2020/06/26 Firebase Realtime Meetup
https://firebase-community.connpass.com/event/175985/

Kenta Kase

June 26, 2020
Tweet

More Decks by Kenta Kase

Other Decks in Programming

Transcript

  1. 多分こんな感じ? type User = { name: string, age: number, }

    const alice: User = { name: 'alice', age: 20 } const docRef = await firestore.collection('users').add(alice) const snapshot = await firestore.collection('users').doc(docRef.id).get() const user = snapshot.data() as User // ここでキャストして型を付ける 毎回collectionを書くのが⾯倒くさい 毎回data()を書くのが⾯倒くさい 毎回キャストで型を付けるのが⾯倒くさい 気軽にキャストしていくのは型ミスに繋がる 5
  2. ⾃分でUserCollectionクラスを作ろう class UserCollection { collection: CollectionReference constructor (firestore: Firestore) {

    this.collection = firestore.collection('users') } async fetch (userId: string) { const snapshot = await this.collection.doc(userId).get() return snapshot.data() as User } } const userCollection = new UserCollection(firestore) // id='alice' を渡すだけ const user = await userCollection.fetch('alice') 6
  3. サンプルケース: FirestoreとTypeScript側でキー名や型が微妙に違う場合 Firestore側 /users : { name: string, age: number,

    created_at: Timestamp } TypeScript側 User: { name: string, age: number, createdAt: Date } こういうケース、たまにありませんか? (むしろ皆さんが普段どうやっているのか知りたい) Firestore-simpleはこういう処理を上⼿く実現できます 11
  4. 素のFirestoreで書く場合 type User = { name: string, age: number, createdAt:

    Date, // js のDate 型で扱いたい } const snapshot = await firestore.collection('users').doc(docRef.id).get() const data = snapshot.data() if (!data) return const user = { name: data.name, age: data.age, createdAt: data.created_at.toDate() // キー名を変換しつつ、Date 型に変換 } as User // 型を揃えたのでここでキャスト 12
  5. Firestore-simpleで書いた場合 type User = { // id は必須プロパティ id: string,

    name: string, age: number, createdAt: Date, } type UserFirestore = { name: string, age: number, created_at: Date, } const firestoreSimple = new FirestoreSimple(firestore) const userCollection = firestoreSimple.collection<User, UserFirestore>({ path: 'users', decode: (doc) => { return { id: doc.id, name: doc.name, age: doc.age, createdAt: doc.created_at.toDate() } } }) const user = await userCollection.fetch('alice') // => { id: 'alice', name: 'alice', // => age: 20, createdAt: 2020-06-20T13:12:54.816Z } 14
  6. Firestore-simpleで書いた場合 collectionのpathを指定するのは⼀度だけ fetchした結果にUser型が⾃動で付く Firestore -> js での変換処理(decode)を書くのも⼀度だけ decode: (doc) =>

    {} の中で doc も型推論されているので補完が効く(今回は 詳しい説明は省略) 逆⽅向のjs -> Firestoreの変換は encode: (obj) => {} (今回は省略) 型に id を必須にしているのは data() をなくしたゆえの制約 id と他のプロパティを同じようにアクセス可能にするため 先の例: user.id (FirestoreのdocumentId), user.name , user.age 15
  7. その他よく使うと思われる機能のショートハンド fetchAll bulkAdd , bulkSet , bulkDelete batchによる⼀括更新処理のラッパー // batch

    でset await userCollection.bulkSet([ { id: 'alice', name: 'alice', age: 20 }, { id: 'bob', name: 'bob', age: 22 }, ]) // colletion の全ドキュメントを取得 const users: User[] = await userCollection.fetchAll() // batch でdelete await userCollection.bulkDelete(users.map((user) => user.id)) 16
  8. 現代ではwithConverterを使うという⼿がある data() の結果に型が付く。Firestore <-> jsの変換も可能 Firestore-simpleの開発を始めた2018年頃にはなかった const userConverter = {

    toFirestore (user: User): FirebaseFirestore.DocumentData { return { name: user.name, age: user.age, created_at: user.createdAt } }, fromFirestore ( data: FirebaseFirestore.DocumentData): User { return { name: data.name, age: data.age, createdAt: data.created_at.toDate() } } } const userCollection = firestore.collection('users').withConverter(userConverter) await userCollection.doc('alice').set({ name: 'alice', age: 20, createdAt: new Date() }) const snapshot = await userCollection.doc('alice').get() const user: User | undefined = snapshot.data() 18
  9. ActiveRecord⾵にはしない // 擬似コードです const user = new User user.name =

    'alice' user.age = 20 await user.save() // ここでDB に書き込まれる const alice = await User.findBy({ name: 'alice') // DB からデータ取得 素のFirestoreと使い⽅がかなり異なる 何かエラーが起きた場合にユーザーがデバッグするのが⼤変になりそう Firestoreのアップデートに追従して機能追加していくのも⼤変そう 22
  10. batch const userCollectionRef = firestore.collection('users') const batch = firestore.batch() batch.set(userCollectionRef.doc('alice'),

    { name: 'alice', age: 20 }) batch.set(userCollectionRef.doc('bob'), { name: 'bob', age: 22 }) await batch.commit() 個⼈的に気に⼊らない点 transactionと挙動が似ているのに、runTransactionとAPIが全く異なる set, update, deleteの使い⽅が普通と異なる addが存在しない doc() とidを空にすると裏でランダム値を⼊れてくれるのでaddは実現可能 なぜ⽤意してくれない・・・ 25
  11. Firestore-simpleによるbatchのサポート 単に配列を⼀括処理したい場合は bulkAdd , bulkSet , bulkdDelete runTransactionと同じように書ける runBatch も⽤意

    await firestoreSimple.runBatch(async (_batch) => { await userCollection.set({ id: 'alice', name: 'alice', age: 20 }) await userCollection.set({ id: 'bob', name: 'bob', age: 20 }) await userCollection.add({ name: 'random', age: 20 }) }) // <- runBatch を抜けるタイミングで⾃動的にbatch.commit() される runBatch の中ではsetなどはすべて⾃動的にbatch内処理として扱われる うっかりbatch外処理として実⾏してしまうミスは起きない 26
  12. Firestore-simpleによるtransactionのサポート runTransactionもrunBatchと同じ改良がされている add, set, update, deleteは⾃動的にtransaction処理として扱われる transactionはgetも同様 ⼀部の処理だけtransaction外で⾏ってしまうという危険なミスは起きない await firestoreSimple.runTransaction(async

    (_tx) => { await userCollection.fetch('alice') await userCollection.fetch('bob') await userCollection.set({ id: 'alice', name: 'alice', age: 20 }) await userCollection.set({ id: 'bob', name: 'bob', age: 22 }) }) // <- runTransaction を抜けるタイミングで⾃動的にtransaction.commit() される 27