我声明了以下类型:
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);
即使foo(bar)
变量与bar:Examples
的函数签名不匹配,Typescript编译器在调用foo
方法时也不会引发任何错误。
为什么打字稿不抛出任何错误?这是编译器中的错误吗?
答案 0 :(得分:5)
之所以可行,是因为交叉点类型可以为其基本类型分配。
作为交集类型,Examples
可分配给ExampleA
。 ExampleA
可分配给{ [key: string]: string }
。因此,Examples
必须可分配给功能参数类型
这可以在以下代码中显示:
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;
partiallyMatchingItem
的属性多于类型中实际声明的属性,这绝对没有问题。保证是最低保证。
但是分配给映射类型无效,因为item
是类型number
的附加成员:
let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item; //Error
因此,这次的保证不是最低保证,而是绝对保证。当您考虑可以轻松地(有意或无意地)绕开它时,这是非常荒谬的:
let item = { a: "Hello World!", b: 1 };
let partiallyMatchingItem: { a: string } = item;
let mappedTypeItem: { [key: string]: string } = partiallyMatchingItem;
或者简单地:
let item = { a: "Hello World!", b: 1 };
let mappedTypeItem: { [key: string]: string } = item as { a: string };
这是一个等待发生的错误,尤其是当您枚举mappedTypeItem
的属性并且假定所有属性的值都是string
时。
考虑到TypeScript中常见的结构化类型分配,这种绝对保证不适合类型系统通常提供的最低保证系统。
一个干净的解决方案是使“常规”类型的值不能分配给映射类型(如果需要向后兼容,则可以使用tsconfig.json
文件中的开关进行切换)。至少您应该避免这种分配,因为此处提供的类型安全性很弱。
答案 1 :(得分:3)
如果您确实要出错,可以将Example
声明为接口,而不是交集类型。 Since 2.2,界面可以扩展对象类型(甚至交集类型)
type ExampleA = {
a: string;
}
type ExampleB = {
b: number;
}
type ExampleC = {
c: boolean;
}
interface Examples extends ExampleA, ExampleB, ExampleC {
}
function foo(pattern: { [key: string]: string }) {
console.log(pattern);
}
const bar: Examples = { a: 'foo', b: 1, c: false };
foo(bar); // error
甚至通过这种方式,可以更好地说明界面和交集类型之间的区别:
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