如何让TypeScript类型系统感知Partial<T>的属性填充状态?
实现逐步填充Partial时的类型细化
泛型函数实现自动类型细化
通过泛型捕获传入对象的具体类型,结合交叉类型将指定属性转为必填,同时保留其他属性的原有可选/必填状态,完美适配缓存复用场景。
代码实现
type PartialUserInfo = { name?: string; age?: number; shoeBrand?: string; }; // 辅助类型:把指定属性转为必填,保留其他属性的原有状态 type FillRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; // 填充用户名的函数 function fillInUserName<T extends PartialUserInfo>( id: string, info: T ): FillRequired<T, 'name'> { if (info.name == undefined) { info.name = lookUpUserName(id); // 假设lookUpUserName是已实现的查询函数 } return info as FillRequired<T, 'name'>; } // 填充鞋品牌的函数 function fillInShoeBrand<T extends PartialUserInfo>( id: string, info: T ): FillRequired<T, 'shoeBrand'> { if (info.shoeBrand == undefined) { info.shoeBrand = lookUpShoeBrand(id); } return info as FillRequired<T, 'shoeBrand'>; } // 使用示例 function doSomeUserStuff(userId: string, info: PartialUserInfo) { // 此时info.name 是 string | undefined,直接访问length会报错(符合预期) // console.log(info.name.length); info = fillInUserName(userId, info); // 类型系统自动识别info.name已为string,可安全访问 console.log(info.name.length); info = fillInShoeBrand(userId, info); // name和shoeBrand均已转为必填,可直接访问 console.log(info.name.length, info.shoeBrand.length); }
核心原理
- 泛型
T会精准捕获传入info的实际类型(比如如果传入的info已经包含必填的age,T会保留这个信息) FillRequired类型通过交叉类型,用-?移除指定属性的可选修饰符,同时保留其他属性的原有状态
断言函数方案(无需重新赋值)
如果不想通过返回值重新赋值,可以用断言函数直接修改原对象并告知类型系统属性已填充:
// 断言用户名已填充 function assertUserNameFilled( id: string, info: PartialUserInfo ): asserts info is FillRequired<PartialUserInfo, 'name'> { if (info.name == undefined) { info.name = lookUpUserName(id); } } // 使用示例 function doSomeUserStuff(userId: string, info: PartialUserInfo) { // info.name 是 string | undefined,直接访问报错 // console.log(info.name.length); assertUserNameFilled(userId, info); // 断言后类型系统自动将info.name转为string console.log(info.name.length); }
通用化填充工具(提升复用性)
如果有大量类似的填充函数,可以封装通用工具函数,避免重复代码:
// 通用填充必填属性的断言函数 function fillRequiredProp<T, K extends keyof T>( obj: T, prop: K, fallback: () => NonNullable<T[K]> ): asserts obj is FillRequired<T, K> { if (obj[prop] == undefined) { obj[prop] = fallback() as T[K]; } } // 使用方式 fillRequiredProp(info, 'name', () => lookUpUserName(userId)); // 执行后info.name自动转为必填
内容的提问来源于stack exchange,提问作者Ipsquiggle
相关产品推荐
相关产品推荐

