You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Firestore嵌套集合创建TypeScript类型安全的getDocument函数

问题描述

我正在使用Firestore数据库,并已创建如下数据模型:

type FirestoreCollection<T> = {
  documentType: T;
  subcollections?: {
    [key: string]: FirestoreCollection<object>;
  };
};

type FirestoreSchema<
  T extends { [key: string]: U },
  U = FirestoreCollection<object>
> = T;

export type FirestoreModel = FirestoreSchema<{
  'chat-messages': {
    documentType: ChatMessage;
  };
  chats: {
    documentType: Chat;
    subcollections: {
      'chat-participants': {
        documentType: ChatParticipant;
      };
    };
  };
}>;

该模型运行正常。我希望为一个函数创建类型定义,该函数接收一组参数,每个参数结构为{ collection: string, id: string },要求当前参数的collection必须是前一个参数对应集合的子集合,否则抛出类型错误。函数的预期行为如下:

// 合法:有效根级文档
getDocument(
  { collection: 'chat-messages', id: '123' }
);

// 合法:'chat-participants'是'chats'的子集合
getDocument(
  { collection: 'chats', id: '123' },
  { collection: 'chat-participants', id: '456' }
);

// 错误:'chats'不是'chat-messages'的子集合
getDocument(
  { collection: 'chat-messages', id: '123' },
  { collection: 'chats', id: '456' }
);

// 错误:'invalid'不是根级集合
getDocument({ collection: 'invalid', id: '123' })

请教实现思路?

实现思路

核心是利用TypeScript的递归类型约束和条件类型,逐步验证每一层集合的父子关系,具体步骤如下:

1. 提取集合的子集合名称类型

先定义工具类型,从指定集合类型中提取它的所有子集合名称:

type GetSubcollections<C extends FirestoreCollection<object>> = 
  C['subcollections'] extends infer S ? 
    S extends { [key: string]: FirestoreCollection<object> } ? keyof S : never : never;

这个类型会返回目标集合的子集合名称联合类型,若没有子集合则返回never。

2. 递归构建合法路径链类型

定义递归类型,描述合法的集合路径链:

  • 第一层参数必须是FirestoreModel中的根集合
  • 后续每一层参数的集合必须是前一层集合的子集合
// 单步路径的类型约束
type ValidPathStep<PrevCollection extends FirestoreCollection<object>> = {
  collection: GetSubcollections<PrevCollection>;
  id: string;
};

// 区分根路径和子路径的类型
type ValidPath<Schema, CurrentCollection extends FirestoreCollection<object> = never> = 
  CurrentCollection extends never ? 
    { collection: keyof Schema; id: string } 
    : ValidPathStep<CurrentCollection>;

// 递归构建完整的路径参数链
type ValidPathChain<Schema, PrevCollection extends FirestoreCollection<object> = never> = 
  | [ValidPath<Schema, PrevCollection>]
  | [ValidPath<Schema, PrevCollection>, ...ValidPathChain<Schema, Schema[ValidPath<Schema, PrevCollection>['collection']]>];

通过递归逻辑,让每一层参数的类型依赖于前一层选择的集合,实现链式的父子关系约束。

3. 绑定类型到函数

将路径链类型应用到函数参数上:

declare function getDocument<Schema extends FirestoreSchema<any>>(
  ...path: ValidPathChain<Schema>
): void;

// 测试用例
getDocument<FirestoreModel>({ collection: 'chat-messages', id: '123' }); // 合法
getDocument<FirestoreModel>({ collection: 'chats', id: '123' }, { collection: 'chat-participants', id: '456' }); // 合法
getDocument<FirestoreModel>({ collection: 'chat-messages', id: '123' }, { collection: 'chats', id: '456' }); // 类型错误
getDocument<FirestoreModel>({ collection: 'invalid', id: '123' }); // 类型错误

优化:自动推导Schema类型

如果不想手动传入FirestoreModel,可以调整函数定义实现自动推导:

declare function getDocument<
  Schema extends FirestoreSchema<any>,
  Path extends ValidPathChain<Schema>
>(...path: Path): void;

// 使用时无需指定Schema
getDocument({ collection: 'chat-messages', id: '123' }); // 合法

内容的提问来源于stack exchange,提问作者Ryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 01:30:10