为Firebase创建跨SDK的包装器(Firestore,Cloud Storage等) [英] Creating a cross-sdk wrapper for Firebase (Firestore, Cloud Storage, and more)

查看:47
本文介绍了为Firebase创建跨SDK的包装器(Firestore,Cloud Storage等)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试寻找一种抽象方法,使我可以在不考虑平台(React Native,React,Node.js)的情况下运行Firebase产品(主要是Firestore,Storage和Analytics).我看过REST API,但想将SDK用于其提供的所有功能.

I'm currently trying to find an abstraction that can allow me to run Firebase products (mainly Firestore, Storage, and Analytics) regardless of the platform (React Native, React, Node.js). I have looked at the REST API but would like to use the SDKs for all the features that they offer.

// web
import firebase from 'firebase';
type WebFirestore = ReturnType<typeof firebase.firestore>;

// cloud
import * as admin from 'firebase-admin';
type CloudFirestore = ReturnType<typeof admin.firestore>;

// native
import { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
type NativeFirestore = FirebaseFirestoreTypes.Module;

const API = (firestore: WebFirestore | CloudFirestore | NativeFirestore) => {
  firestore
    .collection('foo')
    .doc('foo')
    .get()
    .then((resp) => true);
}

我正在尝试创建一个TypeScript类型,该类型可以使我执行相同的操作(至少这是我的想法).一开始,这些产品的API在各个平台之间保持一致,但是我猜想返回类型是不同的.我的意思是,只要firestore对象属于该平台上的SDK,我就可以在所有平台上运行此功能.

I'm trying to create a TypeScript type that can enable me to do the same (at least that's what I think). The API, on the outset, is kept consistent across platforms for these products but my guess is that the return types are different. By that I mean, I can run this function on all platforms as long as the firestore object belongs to the SDK on that platform.

我当时正在考虑创建一个带有标志("web","cloud","native")的类,然后在构造函数中采用firestore对象.我尝试运行下面的代码,但TypeScript表示以下内容:

I was thinking of creating a class that takes a flag ('web', 'cloud', 'native') and then also take the firestore object in the constructor. I tried running the code below but TypeScript says the following:

(property) Promise<T>.then: (<TResult1 = FirebaseFirestore.DocumentSnapshot<FirebaseFirestore.DocumentData>, TResult2 = never>(onfulfilled?: (value: FirebaseFirestore.DocumentSnapshot<FirebaseFirestore.DocumentData>) => TResult1 | PromiseLike<...>, onrejected?: (reason: any) => TResult2 | PromiseLike<...>) => Promise<...>) | (<TResult1 = firebase.firestore.DocumentSnapshot<...>, TResult2 = never>(onfulfilled?: (value: firebase.firestore.DocumentSnapshot<...>) => TResult1 | PromiseLike<...>, onrejected?: (reason: any) => TResult2 | PromiseLike<...>) => Promise<...>) | (<TResult1 = FirebaseFirestoreTypes.DocumentSnapshot<...>, TResult2 = never>(onfulfilled?: (value: FirebaseFirestoreTypes.DocumentSnapshot<...>) => TResult1 | PromiseLike<...>, onrejected?: (reason: any) => TResult2 | PromiseLike<...>) => Promise<...>)
Attaches callbacks for the resolution and/or rejection of the Promise.

@param onfulfilled — The callback to execute when the Promise is resolved.

@param onrejected — The callback to execute when the Promise is rejected.

@returns — A Promise for the completion of which ever callback is executed.

This expression is not callable.
  Each member of the union type '(<TResult1 = DocumentSnapshot<DocumentData>, TResult2 = never>(onfulfilled?: (value: DocumentSnapshot<DocumentData>) => TResult1 | PromiseLike<...>, onrejected?: (reason: any) => TResult2 | PromiseLike<...>) => Promise<...>) | (<TResult1 = DocumentSnapshot<...>, TResult2 = never>(onfulfilled?: (value: DocumentSnapsh...' has signatures, but none of those signatures are compatible with each other.ts(2349)

我是TypeScript的新手,我想知道是否有一种方法可以使这项工作正常进行.所有类型单独工作,但它们的结合不工作.有没有更好的方法来考虑TypeScript中的这一抽象层?我打算将其托管在Github程序包注册表和所有产品上,以使内部API可以作为当前功能使用-Firestore,云存储,云功能,一些REST API调用.

I'm rather new to TypeScript and was wondering if there is a way to make this work. All the types individually work but their union doesn't work. Is there a better way to think about this layer of abstraction in TypeScript? I intend to host this on the Github package registry and all the products to have access to the internal API as functions that are currently - firestore, cloud storage, cloud functions, some REST API calls.

推荐答案

基于 string 标志的切换几乎从来都不是正确"的选择.办法.您想将 if 条件替换为抽象级别.

Switching based on string flag is almost never the "right" way. You want to replace if conditions with a level of abstraction.

您可能想阅读适配器模式,它是一种通用的OOP方法在这种情况下.对于每种类型的商店实例,您将具有一个单独的包装器类,而不是具有 type 属性的一个类.这些类都具有相同的公共API 接口SharedFirestore ,但是在内部,它们可以在其 this.firestore 上调用不同的方法以获取结果.当您要使用Firestore时,只需输入 SharedFirestore 类型,您就会知道可以与之交互,而不论它是哪种存储类型.

You might want to read up on the Adapter Pattern, which is a generalized OOP approach to this sort of situation. Instead of one class with type property, you would have a separate wrapper class for each type of store instance. These classes would all have the same public API interface SharedFirestore, but internally they could call different methods on their this.firestore to get the results. When you want to use a firestore, you would just require the type SharedFirestore and you would know that you could interact with it the same regardless of which store type it is.

这种设置看起来像:

interface SharedFirestore {
  getDoc( collectionPath: string, documentPath: string ): Document;
}

class WebFirestore implements SharedFirestore {
  
  private firestore: firebase.firestore.Firestore;

  constructor( app?: firebase.app.App ) {
    this.firestore = firebase.firestore(app);
  }

  getDoc( collectionPath: string, documentPath: string ): Document {
    return this.firestore.collection(collectionPath).doc(documentPath);
  }
}

class CloudFirestore implements SharedFirestore {

  private firestore: FirebaseFirestore.Firestore;

  constructor( app?: admin.app.App ) {
    this.firestore = admin.firestore(app);
  }

  getDoc( collectionPath: string, documentPath: string ): Document {
    return this.firestore.someOtherMethod( collectionPath, documentPath );
  }
}

打字稿泛型

包装类在这里不是必需的,因为这三种类型的已经实现了相同的接口.它们都允许您通过调用 firestore.collection(collectionPath).doc(documentPath).get()来获取文档.这纯粹是打字稿问题,是由不同的返回类型引起的.

Typescript Generics

Wrapper classes are not necessary here because the three types already implement the same interface, kind of. They all allow you to get a document by calling firestore.collection(collectionPath).doc(documentPath).get(). This is purely a typescript issue which is caused by the differing return types.

web.collection('foo').doc('foo').get();
// type: firebase.firestore.DocumentSnapshot<firebase.firestore.DocumentData>

cloud.collection('foo').doc('foo').get();
// type: FirebaseFirestore.DocumentSnapshot<FirebaseFirestore.DocumentData>

native.collection('foo').doc('foo').get();
// type: FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>

您的 then 回调是文档的功能,但是您不知道三种文档中的哪一种.因此,您无法在联合上调用函数.相反,我们需要说无论我拥有哪种类型的商店,我的回调都将与之匹配".为此,我们使 API 为泛型,这取决于商店的类型.

Your then callback is a function of the document, but you don't know which of the three types of document you have. So you cannot call a function on the union. Instead we need to say "whichever type of store I have, my callback will match that". To do that, we make the API a generic which depends on the store type.

我们可以使用一些条件类型从商店类型中提取集合和文档的关联类型.

We can use some conditional types to extract the associated types for the collection and the document from the store type.

interface BaseCollection<D> {
  doc(path: string): D;
}

interface BaseStore<C extends BaseCollection<any>> {
  collection(path: string): C;
}

type CollectionFromStore<S> = S extends BaseStore<infer C> ? C : never;

type DocFromCollection<C> = C extends BaseCollection<infer D> ? D : never;

type DocFromStore<S> = DocFromCollection<CollectionFromStore<S>>

这是一种可能的设置,该设置使用泛型来扩展基本类型而不是扩展并集.

Here's a possible setup that uses generics to extend a base type rather than extending a union.

class FirebaseAPI <S extends BaseStore<any>> {
  
constructor( private firebase: S ) {}

  getCollection( collectionPath: string ): CollectionFromStore<S> {
    return this.firebase.collection(collectionPath);
  }

  getDoc( collectionPath: string, documentPath: string ): DocFromStore<S> {
    return this.getCollection(collectionPath).doc(documentPath);
  }
}

您可以看到我们如何获得适当的回报类型.

You can see how we get the appropriate return types.

(new FirebaseAPI(web)).getDoc('', '').get().then(v => {});
// v has type firebase.firestore.DocumentSnapshot<firebase.firestore.DocumentData>
(new FirebaseAPI(cloud)).getDoc('', '').get().then(v => {});
// v has type FirebaseFirestore.DocumentSnapshot<FirebaseFirestore.DocumentData>
(new FirebaseAPI(native)).getDoc('', '').get().then(v => {});
// v has type FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>

游乐场链接

这篇关于为Firebase创建跨SDK的包装器(Firestore,Cloud Storage等)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆