TypeScript中已校验可选键仍提示‘可能为undefined’的解决方法
问题
我传入一个包含可选键(如var1?: number)的对象,用arrayEqual函数确保所有键都存在后再使用,但执行const var1 = input.var1 + 3时仍收到'input.var1' is possibly 'undefined'的提示。该如何解决?是否应在传入foo()前进行校验,从而移除inputT中的所有??
代码示例:
interface inputT { var1?: number; var2?: number; } function arraysEqual(a1: string[], a2: string[]) { return JSON.stringify(a1) == JSON.stringify(a2); } function foo(input: inputT): number { if (!arraysEqual(Object.keys(input), ["var1", "var2"])) { return 0; } const var1 = input.var1 + 3; // 这里提示input.var1可能为undefined return var1; } foo({"var1":2});
解决方案
1. 用自定义类型守卫让TS识别校验逻辑
TypeScript没法自动识别arraysEqual的返回值和对象属性存在性的关联,所以得写个类型守卫函数,明确告诉TS:当这个函数返回true时,输入对象的所有可选属性都已存在。
示例代码:
interface inputT { var1?: number; var2?: number; } // 类型守卫:返回true时,input会被TS判定为所有属性必填的版本 function hasAllRequiredKeys(input: inputT): input is Required<inputT> { return "var1" in input && "var2" in input; } function foo(input: inputT): number { if (!hasAllRequiredKeys(input)) { return 0; } // 这里TS自动知道input.var1和input.var2都不为undefined const var1 = input.var1 + 3; return var1; }
这里用Required<inputT>直接生成所有属性必填的类型,比手动写新接口更高效。另外不推荐用Object.keys做对比——它会忽略不可枚举属性,而且键的顺序不匹配也会误判,直接判断属性存在性更可靠。
2. 拆分接口,区分不同输入场景
如果业务里确实存在“属性可能缺失”和“必须全属性”两种输入情况,拆分两个接口会让逻辑更清晰:
// 可选属性的基础接口 interface PartialInputT { var1?: number; var2?: number; } // 全属性必填的接口 interface FullInputT extends Required<PartialInputT> {} // 校验函数:把PartialInputT转为合法的FullInputT,否则返回null function validateInput(input: PartialInputT): FullInputT | null { if ("var1" in input && "var2" in input && typeof input.var1 === "number" && typeof input.var2 === "number") { return input as FullInputT; } return null; } function foo(input: PartialInputT): number { const validInput = validateInput(input); if (!validInput) { return 0; } const var1 = validInput.var1 + 3; return var1; }
这种方式下TS的类型推断会更准确,代码边界也更明确。
3. 提前校验,让foo只处理合法输入
如果foo函数本身只应该接收全属性的对象,那确实应该在调用foo前完成校验,同时把foo的参数类型改成必填:
interface FullInputT { var1: number; var2: number; } function foo(input: FullInputT): number { const var1 = input.var1 + 3; return var1; } // 调用前做校验 const rawInput = {"var1":2}; if ("var1" in rawInput && "var2" in rawInput && typeof rawInput.var1 === "number" && typeof rawInput.var2 === "number") { foo(rawInput); } else { // 处理非法输入的逻辑 console.log("输入缺少必要属性或属性类型错误"); }
这种方式让foo的职责更单一——只处理合法输入,校验逻辑放在外部,代码更易维护。
内容的提问来源于stack exchange,提问作者TungTung
相关产品推荐
相关产品推荐

