在这种情况下,您实际上会从更严格的输入中受益匪浅。通过使用Object,您实际上是关闭所有通过这些函数的数据的输入系统,直到它们可以在其他地方显式地重新输入。这意味着您目前正在丢失大量不需要的输入信息。
这是泛型的教科书案例,记录在 here。
// @flow strict
const hasKey = <T: {}>(o: T): (string => boolean) =>
Object.prototype.hasOwnProperty.bind(o);
const union = <T: {}>(objects: Array<T>): T =>
objects.reduce((acc, o) => ({ ...acc, ...o }), ({}: $Shape<T>));
上面最重要的部分是<T: {}>中的: {}。这些是类型边界。如果泛型是一种说法,“允许用户传递他们想要的任何类型,并将该类型存储在变量中以便我以后可以引用它”,那么type bounds 表示“允许用户传递他们想要的任何类型,只要该类型是 X 类型的成员。”
由于width subtyping 的工作方式,{} 是最通用的对象类型。实际上所有对象都是{} 的子类型。所以<T: {}> 基本上意味着,“T 应该是任何类型的对象。”
请注意,这与<T: Object> 非常不同,<T: Object> 基本上意味着“T 是一个对象,从现在开始我不会检查它的任何其他内容。”这意味着我们可以执行以下操作:
const o: Object = {};
console.log(o.some.member.that.doesnt.exist); // no error at compile time,
// but obvious error at runtime
不同于:
const o: {} = {};
console.log(o.member); // Error, we don't know about this member property!
因此,通过告诉 flow 参数是 {} 的子类型,我们告诉它它具有对象的基本 API。它有属性,它可以被休息和传播,它可以被字符串索引等等,但没有别的了。此外,通过将数据类型存储为通用 T 并返回该类型,我们正在维护参数的类型信息。这意味着无论我们作为参数传入什么,我们都会从另一边得到相同类型的东西(而不是一个神秘的黑匣子)。