TypeScript:经断言验证后变量类型为何重置为unknown?
问题描述
在assertValidString函数中,通过assert<string>断言参数str为string类型后,str的类型确实变为string。但执行if(trimBeforeLenghtValidation) str = str.trim();这行代码后,str的类型变回unknown,导致后续访问str.length时出现错误:Object is of type 'unknown'.(2571)。希望了解该问题的根本原因,不使用any作为临时解决方案。
相关代码片段:
// this class is imported by the validator.ts module class EWC extends Error { constructor(public message: string, public code: string) { super(message); } } // The assert function is coming from an external library // and it has the following signature interface AssertOtherArg { name: string; code: string; } function assert<Type>(schema: string, value: unknown, other: AssertOtherArg): asserts value is Type { throw "unimplemented"; } // end of assert // ------- [string-validator.ts] ---------------------------------------------- interface OtherInfo { name: string; typeErrorCode?: string; trimBeforeLenghtValidation?: boolean; minLength?: number; minLengthErrorCode?: string; maxLength?: number; maxLengthErrorCode?: string; } function assertValidString(str: unknown, otherInfo: OtherInfo): asserts str is string { const {name, trimBeforeLenghtValidation = false} = otherInfo; { const code = "typeErrorCode" in otherInfo ? otherInfo.typeErrorCode : `invalid_${name.toLocaleUpperCase()}`; assert<string>("string", str, {name, code}); } str; // here str: string if(trimBeforeLenghtValidation) str = str.trim(); str; // here str: unknown! why? // min length vlaidation if("minLength" in otherInfo) { const code = "minLengthErrorCode" in otherInfo ? otherInfo.minLengthErrorCode : `${name.toUpperCase()}_TOO_SHORT`; const {minLength} = otherInfo; // the problem is happening here // Error: Object is of type 'unknown'.(2571) if(str.length < minLength) throw new EWC(`"${name}" cannot be shorter than ${minLength} character(s) long!`, code); } // maxLength validation // ... }
根本原因
TypeScript中,函数参数的类型断言(比如asserts value is Type)只会临时窄化参数的类型范围,不会修改参数的原始类型声明。str的原始类型是unknown,当你对它执行赋值操作(str = str.trim())时,TypeScript会重新校验赋值内容是否符合参数的原始类型声明——尽管str.trim()返回string,但由于参数本身被声明为unknown,TypeScript不会保留之前通过断言得到的窄化类型,而是将其重置为原始的unknown类型。
简言之:参数的类型窄化是临时状态,一旦对参数重新赋值,之前的窄化类型信息就会丢失,回到参数最初的声明类型。
解决方案
可以通过以下两种方式避免类型丢失,且无需使用any:
使用临时变量存储窄化后的类型
将断言后的str赋值给一个临时变量,后续所有操作都基于这个临时变量:function assertValidString(str: unknown, otherInfo: OtherInfo): asserts str is string { const {name, trimBeforeLenghtValidation = false} = otherInfo; { const code = "typeErrorCode" in otherInfo ? otherInfo.typeErrorCode : `invalid_${name.toLocaleUpperCase()}`; assert<string>("string", str, {name, code}); } // 用临时变量保存窄化后的string类型 let validatedStr = str; if(trimBeforeLenghtValidation) validatedStr = validatedStr.trim(); if("minLength" in otherInfo) { const code = "minLengthErrorCode" in otherInfo ? otherInfo.minLengthErrorCode : `${name.toUpperCase()}_TOO_SHORT`; const {minLength} = otherInfo; if(validatedStr.length < minLength) throw new EWC(`"${name}" cannot be shorter than ${minLength} character(s) long!`, code); } // maxLength validation // ... }避免重新赋值原参数
直接使用trim()后的结果进行判断,不修改原参数:function assertValidString(str: unknown, otherInfo: OtherInfo): asserts str is string { const {name, trimBeforeLenghtValidation = false} = otherInfo; { const code = "typeErrorCode" in otherInfo ? otherInfo.typeErrorCode : `invalid_${name.toLocaleUpperCase()}`; assert<string>("string", str, {name, code}); } // 直接生成要检查的字符串,不修改原参数 const strToCheck = trimBeforeLenghtValidation ? str.trim() : str; if("minLength" in otherInfo) { const code = "minLengthErrorCode" in otherInfo ? otherInfo.minLengthErrorCode : `${name.toUpperCase()}_TOO_SHORT`; const {minLength} = otherInfo; if(strToCheck.length < minLength) throw new EWC(`"${name}" cannot be shorter than ${minLength} character(s) long!`, code); } // maxLength validation // ... }
内容的提问来源于stack exchange,提问作者h-sifat
相关产品推荐
相关产品推荐

