对象类型(Object Types)
对象类型语法
对象类型尝试尽可能多地匹配JavaScript中的对象的语法。使用形式是:{键: 值}
。
// @flow
var obj1: { foo: boolean } = { foo: true };
var obj2: {
foo: number,
bar: boolean,
baz: string,
} = {
foo: 1,
bar: true,
baz: 'three',
};
对象类型可选属性
在JavaScript中,访问不存在的属性评估为undefined
。这是JavaScript程序中常见的错误来源,所以Flow会将这些错误转化为类型错误。
// @flow
var obj = { foo: "bar" };
// $ExpectError
obj.bar; // Error!
添加一个可选属性的方式是:{属性名称?: 类型}
。
// @flow
var obj: { foo?: boolean } = {};
obj.foo = true; // Works!
// $ExpectError
obj.foo = 'hello'; // Error!
可选属性值可以是void
或省略,但是不能是null
// @flow
function acceptsObject(value: { foo?: string }) {
// ...
}
acceptsObject({ foo: "bar" }); // Works!
acceptsObject({ foo: undefined }); // Works!
// $ExpectError
acceptsObject({ foo: null }); // Error!
acceptsObject({}); // Works!
对象类型推导
Flow有两种不同的方式推导出对象字面量的类型。
密封的对象(Sealed objects)
在Flow中创建一个密封的对象类型的方法是创建一个带有属性的对象。密封的对象知道你声明的所有属性及其值的类型。
// @flow
var obj = {
foo: 1,
bar: true,
baz: 'three'
};
var foo: number = obj.foo; // Works!
var bar: boolean = obj.bar; // Works!
// $ExpectError
var baz: null = obj.baz; // Error!
var bat: string = obj.bat; // Error!
Flow不允许你为密封对象添加新的属性。
// @flow
var obj = {
foo: 1
};
// $ExpectError
obj.bar = true; // Error!
// $ExpectError
obj.baz = 'three'; // Error!
未密封的对象(Unsealed objects)
在Flow中创建一个未密封的对象类型的方法是创建一个带没有属性的对象。未密封的对象不知道你声明的所有属性及其值的类型。
// @flow
var obj = {};
obj.foo = 1; // Works!
obj.bar = true; // Works!
obj.baz = 'three'; // Works!
未密封的对象允许你设置属性。
// @flow
var obj = {};
obj.foo = 42;
var num: number = obj.foo;
为未密封对象属性重新赋值
与var
和let
变量相似,你可以改变未密封的对象的属性值。Flow会为你设置可能的类型。
// @flow
var obj = {};
if (Math.random()) obj.prop = true;
else obj.prop = "hello";
// $ExpectError
var val1: boolean = obj.prop; // Error!
// $ExpectError
var val2: string = obj.prop; // Error!
var val3: boolean | string = obj.prop; // Works!
当Flow可以确定重新分配后的属性类型时,Flow会为其分配确定的类型。
// @flow
var obj = {};
obj.prop = true;
obj.prop = "hello";
// $ExpectError
var val1: boolean = obj.prop; // Error!
var val2: string = obj.prop; // Works!
未密封对象上的未知属性查找是不安全的
未密封的对象允许随时写入新的属性。Flow确保读取与写入兼容,但不能确保写入发生在读取之前(按执行顺序)。
这意味着从未被封装的对象中读取没有匹配的写入从不被检查。这是Flow的不安全行为,未来可能会有所改进。
var obj = {};
obj.foo = 1;
obj.bar = true;
var foo: number = obj.foo; // Works!
var bar: boolean = obj.bar; // Works!
var baz: string = obj.baz; // Works?
确切的对象类型
在Flow中,在预期正常对象类型的情况下传递具有额外属性的对象是安全的。
// @flow
function method(obj: { foo: string }) {
// ...
}
method({
foo: "test", // Works!
bar: 42 // Works!
});
这是一种通常被称为“宽度子类型”的子类型,因为“更宽”(即具有更多属性)的类型是更窄类型的子类型。
有时候阻止这种行为并且只允许一组特定的属性是必要的。为此,Flow有了以下的做法:
{| foo: string, bar: number |}
与常规对象类型不同,将具有“额外”属性的对象传递给确切的对象类型是无效的。
// @flow
var foo: {| foo: string |} = { foo: "Hello", bar: "World!" }; // Error!
映射类(Map类)
JavaScript新标准增加了Map类。在种情况下,一个对象可能会添加一些属性,并在整个生命周期中被检索。而且,属性键甚至可能不被静态检查,所以写出类型注释是不可能的。对于像这样的对象,Flow提供了一种称为“索引器属性”的特殊属性。索引器属性允许使用与索引器键类型匹配的任何键进行读取和写入。
// @flow
var o: { [string]: number } = {};
o["foo"] = 0;
o["bar"] = 1;
var foo: number = o["foo"];
索引器可以被任意命名:
// @flow
var obj: { [user_id: number]: string } = {};
obj[1] = "Julia";
obj[2] = "Camille";
obj[3] = "Justin";
obj[4] = "Mark";
当对象类型具有索引器属性时,即使对象在运行时某个期间没有值,属性访问也被假定为具有注释类型。
var obj: { [number]: string } = {};
obj[42].length; // No type error, but will throw at runtime
索引器属性可以与命名属性混合使用:
// @flow
var obj: {
size: number,
[id: number]: string
} = {
size: 0
};
function add(id: number, name: string) {
obj[id] = name;
obj.size++;
}