TypeScript中同命名空间接口在单/多.d.ts文件中声明的类型检查差异原理咨询
嘿,这个观察真的很细致!我来给你拆解一下背后的原理:
首先说同一文件内的接口合并:
TypeScript对同一作用域(比如同一个文件里的同一个命名空间)下的接口合并有严格的规则——所有同名属性的修饰符(包括可选性?、readonly这类)必须完全一致。你在同一个文件里声明的两个Test.A接口,一个把test定义成可选,一个定义成必选,这属于明显的修饰符冲突,TS直接报错是在帮你避免写出自相矛盾的类型定义。
然后是不同.d.ts文件的情况:
这里涉及到TypeScript针对环境声明文件的特殊合并策略。.d.ts是用来描述已有JavaScript代码的类型文件,TS默认认为不同的环境声明文件可能来自不同的维护者,或者是对同一JS API的不同场景描述,所以会采用更宽松的合并逻辑:
当发现不同文件里的同名接口有冲突的属性修饰符时,TS会自动选择最宽松的兼容版本。具体到你的例子,test?: string是test: string的超类型(可选属性允许undefined,能兼容必选属性的场景,但反过来不行),所以合并后的Test.A最终会采用test?: string的定义,这样才能同时兼容两个声明的潜在意图。
这也是为什么你的类型检查里,Equal<Test.A, { test?: string }>能通过,而和{ test: string }对比会失败——合并后的类型确实是更宽松的可选属性版本。
简单总结的话:同一文件内的代码是你可控的,TS严格校验避免矛盾;环境声明文件是描述外部代码的,TS优先保证类型能正常工作,所以用了宽松的合并策略。
内容来源于stack exchange

