TypeScript条件类型函数实现困惑及联合类型疑问求助
问题解决与解析
一、函数实现修复
你的deserializeObj函数报错核心有两个原因:
- 类型推导逻辑错误:
Omit<Obj, 'id'>的结果并非预期的Omit<ObjA, 'id'> | Omit<ObjB, 'id'>,而是{ common: string }——因为keyof (ObjA | ObjB)仅返回联合类型所有分支的公共键(id和common),去掉id后只剩common属性。 - 条件类型无法自动收窄:即便修正类型定义,TS也无法自动将函数返回值与
DeserializedObj<T>的具体分支匹配,需通过函数重载或类型断言明确类型关系。
修复后代码(推荐函数重载)
import { ObjectID } from "mongodb"; // 假设使用MongoDB的ObjectID type ObjCommon = { id: ObjectID; common: string; }; type ObjA = ObjCommon & { A: number; }; type ObjB = ObjCommon & { B: boolean; }; // 明确序列化分支类型,避开Omit联合类型的坑 type SerializedObjA = Omit<ObjA, 'id'> & { id: string }; type SerializedObjB = Omit<ObjB, 'id'> & { id: string }; type SerializedObj = SerializedObjA | SerializedObjB; // 基于明确分支的条件类型 type DeserializedObj<T> = T extends SerializedObjA ? ObjA : T extends SerializedObjB ? ObjB : never; // 函数重载实现自动类型推导 function deserializeObj(serializedObj: SerializedObjA): ObjA; function deserializeObj(serializedObj: SerializedObjB): ObjB; function deserializeObj(serializedObj: SerializedObj): Obj { const { id, ...objData } = serializedObj; return { ...objData, id: new ObjectID(id), } as Obj; } // 使用示例:自动推导返回类型 const objA = deserializeObj({ id: "123", common: "test", A: 123 }); // 类型为ObjA const objB = deserializeObj({ id: "456", common: "test", B: true }); // 类型为ObjB
若不想用重载,也可通过类型断言修正原函数:
function deserializeObj<T extends SerializedObj>(serializedObj: T): DeserializedObj<T> { const { id, ...objData } = serializedObj; return { ...objData, id: new ObjectID(id), } as DeserializedObj<T>; }
二、联合类型示例解析
Ex1:同时含A、B属性的对象属于Obj,但无法赋值给ObjA
- 符合
Obj类型的原因:TS联合类型规则是「满足任意一个分支即可」。你的t对象同时拥有ObjA和ObjB的所有属性,自然满足ObjA | ObjB的要求。 - 无法赋值给
ObjA的原因:变量t的类型被推断为Obj(联合类型),而非具体的ObjA。联合类型赋值给单个分支时,TS需要明确类型归属,可通过类型缩小解决:if ("A" in t) { const u: ObjA = t; // 此时TS确定t是ObjA,无报错 }
Ex2:缺失A、B属性的对象符合SerializedObj类型
核心原因是keyof (ObjA | ObjB)的特性:联合类型的keyof仅返回所有分支的公共键(即id和common)。因此:
type OmitObjId = Omit<Obj, 'id'>; // 等价于 { common: string } type SerializedObj = OmitObjId & SerializedId; // 等价于 { common: string; id: string }
仅含common和id的对象完全符合SerializedObj的类型定义。若要强制SerializedObj必须包含A或B,需像修复后的代码那样,明确定义SerializedObjA和SerializedObjB的联合类型,而非依赖Omit<Obj, 'id'>。
内容的提问来源于stack exchange,提问作者Isaac Torres
相关产品推荐
相关产品推荐

