为何无法定义继承Window接口的新自定义接口?
我太懂你这种纠结了!之前我也试过类似的写法,踩过一模一样的坑,来给你掰扯清楚为啥会这样:
首先先还原一下你的场景,你写了这样的代码:
export interface IWindowWithMyStuff extends Window { foo: string } const w = window as IWindowWithMyStuff;
然后就碰到了这个扎心的错误:
TS2352: Conversion of type Window & typeof globalThis to type IWindowWithMyStuff may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to unknown first. Property 'foo' is missing in type Window & typeof globalThis but required in type IWindowWithMyStuff
核心原因其实和TypeScript对全局Window类型的特殊处理有关
TypeScript的类型系统是结构类型,但对于Window这种和运行时强绑定的全局内置类型,它有额外的严格校验逻辑:
- 全局的
window对象在TypeScript看来是一个已知结构的实例,它的类型就是内置的Window接口,TypeScript明确知道这个对象上有哪些属性,没有哪些属性。 - 你定义的
IWindowWithMyStuff接口,要求必须包含foo这个必填属性,但TypeScript清楚得很——运行时的window根本没有这个属性!你只是在类型层面凭空加了这个要求,但实际对象并不满足,所以它会判定这个类型转换是不安全的,属于“你可能搞错了”的情况。
和全局接口增强的区别在哪?
很多人推荐的全局接口增强写法,是直接修改TypeScript内置的Window接口:
declare global { interface Window { foo: string; } }
这种写法相当于告诉TypeScript:“嘿,运行时的window对象其实还有foo这个属性,你之前的定义不全”,TypeScript会把这个新属性合并到原有的Window类型里,所以你直接用window.foo也不会报错。但你的写法是创建了一个全新的接口,这个接口的要求比内置Window更高,而实际的window对象达不到这个要求,所以转换失败。
为啥你的命名接口方式不可行?
说白了就是TypeScript的类型安全原则在起作用:它不允许你断言一个已知不包含某属性的对象,必须包含这个属性——这相当于你在类型层面“撒谎”了,TypeScript会阻止你这么做,除非你先转成unknown(相当于强行关掉类型检查),但这就失去了用TypeScript的意义。
如果你非要用命名接口的方式,其实只有一个变通办法:把foo改成可选属性:
export interface IWindowWithMyStuff extends Window { foo?: string; }
这样转换的时候就不会报错,但这显然不符合你希望foo是确定存在的需求,这也侧面印证了TypeScript对这种场景的严格限制。
内容来源于stack exchange

