如何定义仅包含T的任意可选属性的泛型类型IncompleteVariant?
我需要定义一个泛型类型IncompleteVariant<T>,它的属性类型与T一致,但所有属性均为可选(支持空对象{}),用于orInitialState函数中。尝试使用type IncompleteVariant<T> = NonNullable<Partial<T>>时,出现TypeScript错误:
Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. Type 'undefined' is not assignable to type 'T[keyof T]'.ts(2322)
相关函数代码:
/** * Modifies the passed `newState` to fallback on the `initialState` if any property is not set. */ export function orInitialState<T extends object> (newState: IncompleteVariant<T>, initialState: T): T { type Key = keyof T; if(!newState) return initialState; const newerState: T = {...initialState} for(const key of Object.keys(initialState)) { if(newState.hasOwnProperty(key as Key)) newerState[key as Key] = newState[key as Key] } return newerState; }
错误原因
NonNullable<Partial<T>>仅排除了newState整体为null或undefined的情况,但Partial<T>会将每个属性的类型改为T[K] | undefined。当尝试把newState[key]赋值给类型为T的newerState[key]时,TypeScript会认为可能将undefined赋值给不允许undefined的属性,因此抛出错误。
解决方案
方案1:保留Partial<T>定义,添加类型断言或类型守卫
子方案1.1:直接添加类型断言
修改循环内的赋值代码,明确告诉TypeScript当前属性值不是undefined:
for(const key of Object.keys(initialState)) { const keyTyped = key as Key; if(newState.hasOwnProperty(keyTyped)) { newerState[keyTyped] = newState[keyTyped] as T[Key]; } }
同时将IncompleteVariant<T>定义为:
type IncompleteVariant<T> = NonNullable<Partial<T>>;
子方案1.2:使用类型守卫函数
定义一个类型守卫来判断值是否非undefined,让TypeScript自动推断类型:
function isDefined<T>(value: T | undefined): value is T { return value !== undefined; } // 修改函数内的循环逻辑: for(const key of Object.keys(initialState)) { const keyTyped = key as Key; const value = newState[keyTyped]; if(newState.hasOwnProperty(keyTyped) && isDefined(value)) { newerState[keyTyped] = value; } }
此时IncompleteVariant<T>仍可使用NonNullable<Partial<T>>定义。
方案2:重新定义IncompleteVariant<T>,限制可选属性值非undefined
如果业务逻辑要求:只要newState包含某个属性,其值就不能是undefined,可以这样定义IncompleteVariant<T>:
type IncompleteVariant<T> = { [K in keyof T]?: NonNullable<T[K]> };
这个定义会让每个可选属性的类型变为T[K]排除undefined后的类型,确保赋值时不会出现类型不兼容问题。
注意:如果T本身的属性允许undefined,这个方案会自动排除undefined,可能不符合预期,此时建议使用方案1。
内容的提问来源于stack exchange,提问作者Liam Pillay

