为何TypeScript在检查类型后无法收窄unknown类型的属性?
我太懂你这种明明把类型检查都做了一遍,结果TypeScript还是不认的憋屈感了!我之前处理JSON.parse返回的unknown类型时也踩过这个坑,咱们来一步步捋清楚问题出在哪,以及怎么解决。
先把你的代码再贴一遍,方便对照:
interface State { prop: number; } function migrateObj(obj: unknown): null | State { if (!obj || typeof obj !== "object") { return null; } if ("prop" in obj && typeof obj.prop === "number") { return obj; // 这里会报错对吧? } return null; }
问题出在哪?
TypeScript的类型收窄虽然智能,但有个小局限:当你把unknown过滤成object类型后,哪怕你通过"prop" in obj和typeof obj.prop === "number"做了检查,TS只能推断出这个对象是带有prop: number属性的对象(类型是object & { prop: number }),但它不会自动把这个匿名类型和你定义的State接口关联起来。
简单说就是:TS知道这个对象有prop: number,但它不确定这个对象是不是正好符合State的结构(虽然咱们肉眼看完全一样,但TS需要明确的类型关联)。
几个可行的解决办法
用类型断言明确告诉TS
既然你已经做了足够的检查,确保这个对象的结构和State一致,那可以直接用类型断言告诉TS:“相信我,这个obj就是State类型”:if ("prop" in obj && typeof obj.prop === "number") { return obj as State; }这种方式简单直接,但要注意——类型断言是你向TS做的“保证”,如果之后你修改了
State接口但没更新检查逻辑,可能会引入类型错误,所以要确保你的检查逻辑和接口定义完全同步。用类型谓词做更严谨的类型守卫
如果你想让TS更“主动”地识别类型,推荐把类型检查逻辑抽成一个独立的类型守卫函数,用obj is State这种类型谓词:interface State { prop: number; } // 类型守卫函数,返回值是类型谓词 function isState(obj: unknown): obj is State { if (!obj || typeof obj !== "object") { return false; } // 这里需要临时断言一下,因为TS还不知道obj有prop属性 return "prop" in obj && typeof (obj as State).prop === "number"; } function migrateObj(obj: unknown): null | State { if (isState(obj)) { // 这里TS会自动把obj的类型收窄成State return obj; } return null; }这种方式的好处是,类型检查逻辑和业务逻辑分离,而且TS会在调用
isState后自动收窄obj的类型,代码可读性和严谨性都更高。直接在条件里用类型断言辅助检查
如果你不想抽函数,也可以在检查obj.prop的时候先断言,让TS能正确识别属性:if ("prop" in obj && typeof (obj as State).prop === "number") { return obj as State; }这种方式是上面两种的折中,适合简单的类型检查场景。
内容来源于stack exchange

