如何用类型安全的通用抽象基类重构Firebase交互代码
基于Firestore的类型安全基类重构方案
你可以通过泛型基类实现类型安全的代码复用,将ModelA和ModelB中重复的Firestore交互逻辑完全提取到基类中,同时保留各自的类型约束。
原代码问题分析
ModelA和ModelB的核心逻辑高度重复:
- 构造函数逻辑一致,仅依赖集合名称和数据类型
- Firestore转换器的
toFirestore逻辑完全相同,fromFirestore仅依赖子类构造函数和数据类型 - 集合引用的创建逻辑仅差异在集合名称
重构后的实现代码
import { DocumentReference, FirestoreDataConverter, collection, doc } from "firebase/firestore"; // 原有的数据接口 interface A { /** */ } interface B { /** */ } // 泛型基类,约束子类必须提供构造函数和集合名称 abstract class ModelBase<T> { readonly docRef: DocumentReference<this>; constructor(public data: T, id?: string) { // 通过typeof this获取当前子类的静态属性 const collectionRef = (this.constructor as typeof ModelBase).collection; this.docRef = id ? doc(collectionRef, id) : doc(collectionRef); } get id() { return this.docRef.id; } // 静态属性需要在子类中实现 protected static collectionName: string; protected static collection: ReturnType<typeof collection>; protected static firestoreConverter: FirestoreDataConverter<any>; // 静态初始化方法,用于创建集合引用和转换器 protected static init<T>(ctor: new (data: T, id?: string) => ModelBase<T>) { this.firestoreConverter = { toFirestore(modelObj: ModelBase<T>) { return modelObj.data || {}; }, fromFirestore(snapshot, options?) { const data = snapshot.data(options) as T; return new ctor(data, snapshot.id); } }; this.collection = collection(db, this.collectionName).withConverter(this.firestoreConverter); } } // 子类ModelA class ModelA extends ModelBase<A> { protected static collectionName = "ModelA"; constructor(data: A, id?: string) { super(data, id); } } // 初始化ModelA的Firestore资源 ModelA.init<A>(ModelA); // 子类ModelB class ModelB extends ModelBase<B> { protected static collectionName = "ModelB"; constructor(data: B, id?: string) { super(data, id); } } // 初始化ModelB的Firestore资源 ModelB.init<B>(ModelB);
关键类型安全保障
- 泛型约束:基类
ModelBase<T>中的T对应子类的数据接口(A或B),确保data属性类型完全匹配 - 构造函数类型校验:
init方法接收的ctor参数约束为new (data: T, id?: string) => ModelBase<T>,保证子类构造函数符合要求 - 静态属性类型继承:通过
typeof this在基类中访问子类的静态属性,避免类型丢失 - 转换器类型匹配:转换器的
fromFirestore方法返回的实例类型与子类完全一致,Firestore的DocumentReference类型也会自动关联到当前子类
优化点说明
- 基类设为
abstract,避免直接实例化 - 静态初始化方法
init统一处理集合引用和转换器的创建,子类只需提供集合名称 - 所有重复逻辑完全收拢到基类,子类仅需关注自身的数据类型和集合名称
内容的提问来源于stack exchange,提问作者nuric
相关产品推荐
相关产品推荐

