TypeScript扩展类型时如何避免未定义错误?
哥们,我太懂你这种憋屈的感觉了——明明已经加了属性存在性检查,TypeScript还是死咬着“Property 'defaultItems' does not exist on type 'BaseProps'”报错,就像没看到你的检查一样。我来给你捋清楚问题出在哪,再给你几个正经的解决办法。
为什么你的检查没生效?
你用StrictUnion把Props变成了互斥联合类型,这点没问题,但TypeScript的类型守卫有个局限:它没办法自动把嵌套属性的检查,反向关联到外层的联合分支上。
举个例子,当你写if (defaultData.defaultItems)时,TS首先会纠结:defaultData可能是BaseDefaultData,而这个类型根本没有defaultItems属性,所以这个访问本身在TS眼里就是“非法”的,直接提前报错了,根本等不到运行时的检查生效。就算你用了可选链defaultData.defaultItems?.results,TS还是会因为BaseDefaultData没有defaultItems而报错——它没办法从可选链的使用里推断出“当前defaultData应该是ExtendedDefaultData”。
正确的解决办法
办法1:给整个Props做类型守卫
写一个类型谓词函数,直接判断当前的props是不是ExtendedProps,这样TS就能明确知道分支里的类型:
// 定义类型谓词,告诉TS这个函数能判断props的类型 function isExtendedProps(props: Props): props is ExtendedProps { // 这里需要临时断言一下,因为BaseDefaultData没有defaultItems,TS会阻止直接访问 return !!(props.defaultData as ExtendedDefaultData).defaultItems; } // 现在用这个守卫包裹代码,TS就不会报错了 if (isExtendedProps(props)) { props.defaultData.defaultItems.function(doSomething); } // 可选链的场景也可以用三元表达式替代 const results = isExtendedProps(props) ? props.defaultData.defaultItems.results : [];
办法2:针对defaultData单独做类型守卫
如果不想针对整个Props做判断,也可以聚焦于defaultData本身,判断它是不是ExtendedDefaultData:
// 判断defaultData是否包含defaultItems属性 function hasDefaultItems(data: BaseDefaultData | ExtendedDefaultData): data is ExtendedDefaultData { return 'defaultItems' in data; } // 使用示例 const { defaultData } = props; // 处理results的场景 const results = hasDefaultItems(defaultData) ? defaultData.defaultItems.results : []; // 调用函数的场景 if (hasDefaultItems(defaultData)) { defaultData.defaultItems.function(doSomething); }
关于你用defaultData['defaultItems']的问题
这种写法确实能绕过TS的检查,但本质是“欺骗”TS,相当于放弃了类型安全——万一哪天你把defaultItems拼写成defaultItem,TS根本不会提醒你,等到运行时才会出问题,所以非常不推荐这么做。
备注:内容来源于stack exchange,提问作者Alyona

