Typescript交集类型和函数签名未引发预期错误

Typescript交集类型和函数签名未引发预期错误,typescript,Typescript,我声明了以下类型: type ExampleA = { a: string; } type ExampleB = { b: number; } type ExampleC = { c: boolean; } type Examples = ExampleA & ExampleB & ExampleC; 然后我使用如下类型: function foo(pattern: { [key: string]: string }) {

我声明了以下类型:

type ExampleA = {
    a: string;
}

type ExampleB = {
    b: number;
}

type ExampleC = {
    c: boolean;
}

type Examples = ExampleA &
    ExampleB &
    ExampleC;
然后我使用如下类型:

function foo(pattern: { [key: string]: string }) {
    console.log(pattern);
}

const bar: Examples = { a: 'foo', b: 1, c: false }; 
foo(bar);
typescript编译器在调用
foo(bar)
方法时没有抛出任何错误,即使
bar:Examples
变量与
foo
的函数签名不匹配


为什么typescript没有抛出任何错误?这是编译器中的一个bug吗?

之所以这样做,是因为交集类型可分配给它的基类型

作为交叉口类型,
Examples
可分配给
ExampleA
<代码>示例a可分配给
{[key:string]:string}
。因此,
示例
必须可分配给函数参数类型

这可以在以下代码中显示:

const bar: Examples = { a: 'foo', b: 1, c: false }; 
const bar2: ExampleA = bar;
const bar3: { [key: string]: string } = bar2;
foo(bar3); //This works
foo(bar2); //Since the assignment bar3 = bar2 works, this must work, too
foo(bar); //Since the assignment bar2 = bar works, this must work, too


更新

当你想坚持“当A可分配给B,B可分配给C,那么A必须可分配给C”的原则时,这种行为是相应的。类型系统除了允许此类赋值之外别无选择。但是,将值作为参数传递给
foo
实际上还有另一个问题

可以将值指定给仅共享指定值的一部分成员的类型的变量。所以这个任务很好:

let item: { a: string, b: number } = { a: "Hello World!", b: 1 };
let partiallyMatchingItem: { a: string } = item;
partiallyMachingitem
的属性比类型中实际声明的属性多绝对没有问题。担保是最低限度的担保

但是,对映射类型的赋值不起作用,因为
项的类型
编号
的附加成员:

let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item; //Error
因此,这次担保不是最低担保,而是绝对担保。这很荒谬,当你考虑你能轻易地绕过它(有意或无意):

或者简单地说:

let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item as { a: string };
这是一个等待发生的错误,尤其是当您通过
mappedTypeItem
的属性进行枚举,并且假设所有属性的值都是
字符串时

考虑到TypeScript中的结构类型赋值是多么常见,这种绝对保证不适用于通常由类型系统提供的最低保证系统


一个干净的解决方案是使“常规”类型的值不可分配给映射类型(如果需要向后兼容,可以在
tsconfig.json
文件中使用开关切换)。至少您应该避免此类赋值,因为此处提供的类型安全性非常弱。

如果您确实希望出现错误,可以将
示例
声明为接口,而不是交叉点类型,接口可以扩展对象类型(甚至交叉点类型)

甚至通过这种方式,为了更好地说明接口类型和交叉点类型之间的差异:

type Examples = ExampleA & // the same as in question
    ExampleB &
    ExampleC;

interface IExamples extends Examples { // empty interface "collapses" the intersection 
}

const bar1: Examples = { a: 'foo', b: 1, c: false };  
foo(bar1); // no error

const bar2: IExamples = { a: 'foo', b: 1, c: false };  
foo(bar2); // error
按照Titian在评论中的建议,在交叉点之外构造对象类型的另一种方法是使用映射类型,该类型几乎(但不完全)与其通用参数相同:

type Id<T> = { [P in keyof T]: T[P] } 

const bar3: Id<ExampleA & ExampleB & ExampleC> = { a: 'foo', b: 1, c: false };

foo(bar3); // error
type Id={[P in keyof T]:T[P]}
constbar3:Id={a'foo',b:1,c:false};
foo(bar3);//错误

不是100%确定,但在我看来像个bug。我认为编译器扩大了
ExampleA
的类型以匹配
{[key:string]:string}
是的,我同意。事实上,您可以将
foo
定义为
函数foo(模式:{[key:string]:string}{const c=pattern['c'];console.log(c);}
,但它仍然不会给出错误,尽管在函数中,
c
被识别为
字符串
,如果您将
示例作为参数传递,则该错误将不为真。@Fmrubio您是否将该错误归档?如果你愿意,我可以做。如果您确实提交了错误,请在此处发布链接不正确,该类型意味着所有现场总线的类型均为字符串,这将给出一个错误,
boo
的类型在理论上应与
示例
常量条:{a:'foo',b:1,c:false}相同;富(巴);type Id={[P in keyof T]:T[P]}const bar:Id={a:'foo',b:1,c:false};富(酒吧);//错误
是的,我在中编辑过,请随意修改,谢谢
type Examples = ExampleA & // the same as in question
    ExampleB &
    ExampleC;

interface IExamples extends Examples { // empty interface "collapses" the intersection 
}

const bar1: Examples = { a: 'foo', b: 1, c: false };  
foo(bar1); // no error

const bar2: IExamples = { a: 'foo', b: 1, c: false };  
foo(bar2); // error
type Id<T> = { [P in keyof T]: T[P] } 

const bar3: Id<ExampleA & ExampleB & ExampleC> = { a: 'foo', b: 1, c: false };

foo(bar3); // error