TypeScript中如何基于类的accessorName生成ServiceMap静态类型?
解决方案:用子类
accessorName构建带静态类型的ServiceMap 要实现用子类的accessorName作为ServiceMap的键,同时保留完整的类型检查,需要调整类型定义和实例化逻辑,具体步骤如下:
1. 调整基础服务类(可选但推荐)
将基础Service类改为抽象类,强制子类必须实现accessorName静态属性,避免遗漏:
import { PrismaClient } from "@prisma/client"; // 提前声明ServiceMap类型,避免循环引用 export type ServiceMap = Record<string, Service>; export default abstract class Service { public static abstract readonly accessorName: string; constructor(protected prisma: PrismaClient, protected services: ServiceMap) {} }
2. 统一导出服务构造函数数组
创建一个包含所有服务类的常量数组,并使用as const让TypeScript保留每个类的具体类型(包括accessorName的字面量值):
// services/index.ts import Users from "./Users"; import Warehouses from "./Warehouses"; import Depots from "./Depots"; // 用as const固定类型,让TS能捕获每个类的accessorName具体值 export const serviceConstructors = [Users, Warehouses, Depots] as const;
3. 构建正确的ServiceMap类型
通过映射类型遍历服务构造函数数组,提取每个类的accessorName作为键,实例类型作为值:
// services/index.ts import type { Service } from "./Service"; // 提取构造函数数组的类型 type ServiceConstructorTuple = typeof serviceConstructors; // 构建最终的ServiceMap类型:键是accessorName,值是对应类的实例 export type ServiceMap = { [K in ServiceConstructorTuple[number] as K["accessorName"]]: InstanceType<K>; };
此时生成的ServiceMap类型完全符合需求:
type ServiceMap = { users: Users; warehouses: Warehouses; depots: Depots; };
4. 实现类型安全的createServiceMap函数
修改实例化逻辑,逐个使用accessorName作为键填充map,同时解决类型断言问题:
// createServiceMap.ts import { PrismaClient } from "@prisma/client"; import { serviceConstructors, ServiceMap } from "./services"; export default function createServiceMap(prisma: PrismaClient): ServiceMap { const map: Partial<ServiceMap> = {}; // 遍历每个服务构造函数,用accessorName作为键创建实例 for (const ServiceClass of serviceConstructors) { const key = ServiceClass.accessorName as keyof ServiceMap; // 断言map为ServiceMap,因为我们会逐步填充完整 map[key] = new ServiceClass(prisma, map as ServiceMap); } return map as ServiceMap; }
为什么之前的方案有问题?
- 键名不符:
import * as services导出的键是变量名(类名Users/Warehouses),而非子类的accessorName属性值,所以生成的ServiceMap键是类名而非目标字符串。 - 类型错误:用
typeof Service作为构造函数类型时,实例化结果被推断为基础Service类型,无法匹配ServiceMap中具体子类的实例类型;改用构造函数数组后,TypeScript能推断每个类的具体实例类型,避免类型不兼容问题。
内容的提问来源于stack exchange,提问作者dimiguel
相关产品推荐
相关产品推荐

