Flow中可空对象不可变属性细化问题:如何传递经细化的对象?
这个场景我太熟悉了——哪怕用了不可变属性修饰符+,Flow还是没法把单个属性的细化结果“同步”到整个对象的类型上,导致传递整个对象时类型不兼容。这是因为Flow的类型细化是针对单个值的,而不是对象的整体类型,哪怕你是const声明的不可变对象,它也不会自动把{+foo: ?string}升级成{+foo: string}。
下面给你几个实用的解决办法:
方法1:构造符合目标类型的新对象(最安全)
既然Flow认单个细化后的属性,那我们就把这个属性抽出来,构造一个完全符合函数参数类型的新对象:
/* @flow */ const a: {+foo: ?string} = {}; const fun = (obj: {+foo: string}) => { return obj } if (a.foo) { // 用已经被细化成string的a.foo构造新对象 fun({ foo: a.foo }); }
这种方式完全符合Flow的类型检查逻辑,没有任何安全风险,也是最推荐的做法。如果你的函数其实只需要foo这个属性,甚至可以直接修改函数参数为单个string类型,直接传a.foo更简洁。
方法2:使用类型断言(谨慎使用)
如果你能100%确定此时a的foo属性不可能为null/undefined(比如这里是const声明的不可变对象,检查后不会被修改),可以用类型断言直接告诉Flow当前对象的类型:
/* @flow */ const a: {+foo: ?string} = {}; const fun = (obj: {+foo: string}) => { return obj } if (a.foo) { // 明确断言a此时符合目标类型 fun((a: {+foo: string})); }
注意:这种方式会绕过Flow的类型检查,所以只在你能完全保证代码安全的场景下使用,避免后续代码变更导致潜在的null风险。
方法3:用Flow高级类型定义细化后的类型
如果你经常需要处理这种“从可选属性到必选属性”的类型转换,可以用Flow的$ObjMapped工具类型提前定义细化后的类型,再配合断言使用:
/* @flow */ // 定义基础可选类型 type MaybeFoo = {+foo: ?string}; // 把所有可选属性转换为必选属性的类型 type RequiredFoo = $ObjMapped<MaybeFoo, <T>(?T) => T>; const a: MaybeFoo = {}; const fun = (obj: RequiredFoo) => { return obj } if (a.foo) { fun(a as RequiredFoo); }
这种方式比直接写断言更具可读性,适合在大型项目中统一管理这类类型转换。
为什么不可变属性还是会报错?
再补充一下原理:Flow的类型系统是结构类型系统,它不会跟踪单个对象的运行时状态变化。当你检查a.foo时,Flow只知道当前这个a.foo的值是string,但不会自动修改整个a的声明类型——哪怕是不可变属性,Flow的设计逻辑里也不会做这种“对象整体类型升级”的操作,所以直接传递a就会触发类型不兼容的报错。
内容的提问来源于stack exchange,提问作者iofjuupasli

