我想将具有确切对象参数的对象传递给函数,而不必指定完全匹配项:
/* @flow */
type a = {|
b: string,
c: number,
d: {| e: number, f: string |},
|}
interface props { b: string, d: { e: number } }
function foo(x: props) {
console.log(`${x.b}: ${x.d.e}`);
}
let var_a: a = {
b: 'b',
c: 0,
d: { e: 1, f: 'f' }
};
foo(var_a)
不幸的是,流量0.78.0给出:
19: foo(var_a)
^ Cannot call `foo` with `var_a` bound to `x` because inexact object type [1] is incompatible with exact object type [2] in property `d`.
References:
8: interface props { b: string, d: { e: number } }
^ [1]
5: d: {| e: number, f: string |},
^ [2]
19: foo(var_a)
^ Cannot call `foo` with `var_a` bound to `x` because property `f` is missing in object type [1] but exists in object type [2] in property `d`.
References:
8: interface props { b: string, d: { e: number } }
^ [1]
5: d: {| e: number, f: string |},
^ [2]
我也尝试使用类型代替接口:
type props = { b: string, d: { e: number } }
现在,可以通过将d
指定为确切元素来轻松解决此问题:
type props = { b: string, d: {| e: number, f: string |} }
这很烦人,因为我想在函数中指定最少数量的参数,即f
从未使用过foo
参数,因此我认为这不是必需的
您可以在Try Flow here
中找到代码答案 0 :(得分:1)
我对此做了进一步的试验,并确定了为什么会发生这种情况,但是我不确定我对答案是否满意。
AFAICT,这仅是功能子类型的怪癖。
// @flow
const Clapper = (opts:{style:string}) => { console.log('clapping '+opts.style); };
const InvokeClapper = (fn:({})=>void) => { fn({}) };
InvokeClapper(Clapper); // fails :(
const Clapper2 = (opts:{}) => { console.log('clapping'); };
InvokeClapper(Clapper2); // works!
Clapper({}); // works!
Clapper2({}); // works!
const a = (b:{}) => 1;
a({}); // works
a({style:'string'}); // works
const a2 = (b:({a:number})=>void) => 1;
const a3 = (b:({|a:number|})=>void) => 1; // this and the above behave the same. why does it assume exact type for function param?
a2( (b:{a:number})=>{} ); // works
a2((b:{a:number,style:string})=>{}); // fails :(
const c2 = (b:({[string]:any})=>void) => 1;
c2((b:{a:number,style:string})=>{}); // works
const c3 = (b:(any)=>void) => 1;
c3((b:{a:number,style:string})=>{}); // works
const c4 = (b:({})=>void) => 1;
c4((b:{a:number,style:string})=>{}); // fails
// how can i say: a function that takes any object?
// or a function which takes any subtype of this object? any object more specific than { a:string, ... }
// this is the textbook example from Flow docs Width Subtyping page; very similar
const c5 = (b:{foo:string}) => 1;
c5({foo:"test", bar:42}); // works!
const c6 = (b:({foo:string})=>void) => 1;
// i guess this is a type=>type comparison, not a value=>type comparison
c6((x:{foo:string,bar:number})=>{}); // fails
c6((x:{foo:string})=>{}); // works
// this is one solution which seems acceptable but i am still confused why is necessary?
const c7 = (b:(x:{foo:string,[string]:any})=>void) => 1;
// what I want to express is: function b promises, for its x object parameter,
// to read/write only on x.foo.
// or, if it attempted to read/write other keys, that should be a compile-time error.
c7((x:{foo:string,bar:number})=>{}); // works
// since exact match seems to do nothing here, i almost wish we could change its meaning,
// so that instead of forcing you to pass a variable with no additional keys,
// it instead forced the function b to not attempt to access or write to keys other than those in the exact match.
const c8 = (b:({|foo:string|})=>void) => 1;
c8((x:{foo:string,bar:number})=>{}); // fails
const altc8: ({foo:string})=>void = (x:{foo:string,bar:number})=>{}; // fails; but using lovely shorter syntax from docs
// reading chapter "Subsets & Subtypes" > "Subtypes of functions"
// it seems like, as in the doc example function f3,
// "The subtype must accept at least the same inputs as its parent,
// and must return at most the same outputs."
const c9 = (b:({foo:string,bar:number})=>number|void) => 1; // this is the parent
c9((x:{foo:string})=>{return undefined;}); // works // so this is the subtype
// i dislike this though.
// from my perspective, it shouldn't matter how many keys on the object in parameter 1.
// they should just both be considered an inexact object of any number of keys.
// while the parent considers it a sealed/exact object of just one key [that it cares about]. arrgh...
// going with solution c7 for now
请参阅: https://flow.org/en/docs/lang/subtypes/#toc-subtypes-of-functions
更新:
我后来发现下一个问题是,对象类型中的函数会自动被视为只读/协变量(即{ +yourFn: ()=>void }
),而{[string]:any}
映射类型会自动将所有键标记为读/写。因此,如果您的子类型包含函数,则它将被拒绝,因为函数在子类型中是只读的,而在父类型中是读/写的。
我通过在父级中联合泛型(作为映射类型的替代方案)并在函数调用时根据需要指定泛型参数来解决THAT问题。
ie。,而不是:
const fn: ({ a:number, [string]: any })=>void = (x:{ a:number, b:number })=>{};
fn({a:1, b:2});
我去了:
const fn2 = <V>(x:{a:number} & V)=>{};
fn2<{b:number}>({ a:1, b:2 });
因此,将问题延迟到调用为止。它仍然提供编译时检查。到目前为止运作良好。看来很厉害! Flow类型的系统比Java中的强大。真是个转折!