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}相同;富(巴);这真的,真的,真的,错了。试着用简单的对象调用自己的例子,这些对象没有所有字段作为索引的返回值,它会给出一个错误。计数器示例:@ fMrBiBio:如果你认为这是一个bug,你必须接受断点改变,交叉类型不再被分配给它们的任何基本类型。当您与其他成员非常常见的接口相交时,这可能会成为一个真正的问题。没有其他办法不打破“当A可分配给B,B可分配给C,那么A必须可分配给C”的基本原则。我发现一条评论证实了这个答案:。另外,确认两种类型的交集不同于具有两种类型属性的对象类型。我还建议使用映射类型: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