TypeScript函数式领域驱动设计中Query映射类型的作用解析
解析TypeScript映射类型定义的Query类型
一、Query类型到底怎么工作?
先拆解这段类型代码:
type Query<T> = { [k: keyof T]: { operator: Operator, value: unknown }, }
keyof T:提取泛型参数T的所有属性名。比如T是{id: string, name: string, age: number},那keyof T就是"id" | "name" | "age"这个联合类型。[k: keyof T]:这是TypeScript映射类型的核心语法,相当于遍历T的每一个属性名k,为每个k自动生成对应的属性结构。- 每个属性对应的
{ operator: Operator, value: unknown }:规定查询某个字段时,必须传入包含操作符(比如等于/大于/包含这类,文章里应该有定义Operator的具体取值)和匹配值的对象。
举个实际例子,假设领域实体是用户:
type User = { id: string; name: string; age: number };
那Query<User>会自动生成对应的查询类型:
type UserQuery = { id: { operator: Operator; value: unknown }; name: { operator: Operator; value: unknown }; age: { operator: Operator; value: unknown }; };
这时候你写查询条件时,只能用User里存在的属性,比如{ age: { operator: "gt", value: 18 } },要是瞎写个address这类不存在的字段,TypeScript会直接报错拦截。
二、在Repository场景下的优势
- 杜绝低级错误:新手常犯的属性名拼写错误(比如把
age写成ag)会被编辑器立刻检测到,不用等到运行时才发现查不到数据。 - 适配所有实体,减少重复代码:不管Repository操作的是用户、订单还是商品,Query类型都会跟着实体的属性自动生成对应的查询结构,不用为每个实体单独写一套查询类型。
- 强制查询格式统一:所有人写查询都必须遵循
{ 字段名: { operator: ..., value: ... } }的格式,不会出现有人直接传{ age: 18 },有人传{ age: { val: 18 } }这种混乱写法,后续getMatching方法的处理逻辑也能统一。 - 贴合DDD领域纯净性:在领域驱动设计中,Repository是领域层和数据层的桥梁,这种类型约束能保证所有查询都严格围绕领域实体的属性展开,不会出现与领域无关的查询条件,避免污染领域模型。
小优化建议
原代码里的value: unknown可以改成value: T[k],这样匹配值的类型会和实体属性的类型严格对应。比如User的age是number,那value就必须传number,不能传字符串,进一步提升类型安全性:
type Query<T> = { [k: keyof T]: { operator: Operator; value: T[k] }; };
内容的提问来源于stack exchange,提问作者user1790300
相关产品推荐
相关产品推荐

