【问题标题】:Creating a cross-sdk wrapper for Firebase (Firestore, Cloud Storage, and more)为 Firebase(Firestore、Cloud Storage 等)创建跨 sdk 包装器
【发布时间】:2021-05-25 02:21:46
【问题描述】:

我目前正在尝试找到一种抽象,它可以让我运行 Firebase 产品(主要是 Firestore、Storage 和 Analytics),而不受平台(React Native、React、Node.js)的影响。我查看了 REST API,但希望将 SDK 用于它们提供的所有功能。

// 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,我就可以在所有平台上运行此函数。

我正在考虑创建一个带有标志('web'、'cloud'、'native')的类,然后在构造函数中使用 firestore 对象。我尝试运行下面的代码,但 TypeScript 显示如下:

(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 调用。

【问题讨论】:

  • TypeScript 错误显示在 .then(...)

标签: node.js typescript firebase react-native google-cloud-firestore


【解决方案1】:

基于string 标志的切换几乎从来都不是“正确”的方式。您想用抽象级别替换 if 条件。

适配器模式

您可能想阅读Adapter Pattern,这是针对这种情况的通用 OOP 方法。代替具有type 属性的一个类,您将为每种类型的商店实例提供一个单独的包装类。这些类都将具有相同的公共 API interface SharedFirestore,但在内部,它们可以在 this.firestore 上调用不同的方法来获取结果。当您想使用 Firestore 时,您只需要 SharedFirestore 类型,您就会知道无论它是哪种类型的商店,您都可以与它进行相同的交互。

这种设置看起来像:

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 );
  }
}

Typescript 泛型

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

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 设为泛型,这取决于商店类型。

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

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>>

这是一个可能的设置,它使用泛型来扩展基类型而不是扩展联合。

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);
  }
}

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

(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>

Playground Link

【讨论】:

    猜你喜欢
    • 2021-12-25
    • 2020-08-15
    • 2020-05-02
    • 2019-03-28
    • 2020-06-21
    • 2018-06-06
    • 2021-03-06
    • 2020-05-18
    • 1970-01-01
    相关资源
    最近更新 更多