Hey all. I'm trying to typecheck my flux actions. They have a type property and an optional payload property. Basically, I want these to work:
const action1: Action = { type: 'DO_STUFF' };
const action2: Action = { type: 'OTHER_STUFF', payload: 'args' };
And these to throw an error:
const action3: Action = { typ: 'TYPO' }; // no "type" property
const action4: Action = { type: 'TYPE', randomKey: 'nahh' }; // extra undeclared property
I've tried the following Action definitions, which don't catch action4 above:
// doesn't throw an error for "action4" above
type Action = { type: string, payload?: any };
// throws an error for "action1", still doesn't throw for "action4"
class Action { type: string; payload: ?any; }
Is there a way to express my constraints cleanly in the Flow type system? I'm looking for something like a sealed type definition that allows optional properties but no undefined properties.
EDIT: actually, this doesn't even seem to work, it still doesn't catch the action4 problem.
I ended up with the following, but it doesn't feel like a good general solution:
class BaseAction {
type: string;
}
class ActionWithPayload extends BaseAction {
payload: any;
}
export type Action = BaseAction | ActionWithPayload;
Flow has this problem with sealed objects: they are checked only after construction. So currently one needs to access the fields to be able to see the errors (statically!). Makes refactoring harder, as renamed fields can stay lurking in objects, affecting serialization etc.
var o : { a: string } = {
a: 'a',
b: 'b',
}; // no error!
o.a;
o.b; // Error: 'Property not found'
Object.keys(o); JSON.stringify(o); ... // no error
This is a major problem in our project, which causes us to use hacks like:
type Foo {
prop: boolean,
prop2?: ?boolean,
// Mark any other properties as invalid.
[key: string]: void
};
This successfully catches instantiation, but actually causes problems when assigning to prop2!
var a:Foo = {
prop: true,
prop2: false,
badProp: true
};
a.prop2 = true;
throws:
models/test.js:75
75: prop2?: ?boolean,
^^^^^^^ boolean. This type is incompatible with
77: [key: string]: void
^^^^ undefined
models/test.js:75
75: prop2?: ?boolean,
^^^^^^^ null. This type is incompatible with
77: [key: string]: void
^^^^ undefined
models/test.js:83
83: badProp: true
^^^^ boolean. This type is incompatible with
77: [key: string]: void
^^^^ undefined
I expect the last error (this is good), but the first two errors shouldn't throw.
So it appears we can either guard against bad properties at construction, or after construction, but not both.
@STRML it's been a while, is this still considered the best approach possible at the moment?
As far as I know yes, no change.
On Apr 30, 2016 3:59 AM, "Stephan Behnke" [email protected] wrote:
@STRML https://github.com/STRML it's been a while, is this still
considered the best approach possible at the moment?—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/facebook/flow/issues/1280#issuecomment-215947832
I'd like to throw my hat in for this to be fixed - it doesn't seem like flow is respecting the notion of sealed objects at all, and this is a key feature for us to be able to verify the shape of a state object. Also, the problem @STRML mentioned with dynamically assigning to properties when [key: string]: void is specified doesn't seem to have anything to do with optional or nullable properties, anything throws that error. See https://gist.github.com/jrajav/90400fe4394f4064ec725ae0aec4f75c
Looks like you can do this now with width subtyping.
type Action = {| type: string, payload?: any |};
const action3: Action = { typ: 'TYPO' }; // no "type" property
const action4: Action = { type: 'TYPE', randomKey: 'nahh' }; // extra undeclared property
Gives me the desired errors:
funcs/test.js:14
14: const action3: Action = { typ: 'TYPO' }; // no "type" property
^^^^^^^^^^^^^^^ property `typ`. Property not found in
14: const action3: Action = { typ: 'TYPO' }; // no "type" property
^^^^^^ object type
funcs/test.js:15
15: const action4: Action = { type: 'TYPE', randomKey: 'nahh' }; // extra undeclared property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `randomKey`. Property not found in
15: const action4: Action = { type: 'TYPE', randomKey: 'nahh' }; // extra undeclared property
^^^^^^ object type
Use disjoint unions with exact object types.
Further, you probably want to mark the object keys as covariant so they are read-only.
You can read more about it here.
Will update the docs, but instead of this:
type Action =
| { type: "FOO", foo: number }
| { type: "BAR", bar: boolean }
| { type: "BAZ", baz: string };
Use this:
type Action =
| {| +type: "FOO", +foo: number |}
| {| +type: "BAR", +bar: boolean |}
| {| +type: "BAZ", +baz: string |};
Most helpful comment
I'd like to throw my hat in for this to be fixed - it doesn't seem like flow is respecting the notion of sealed objects at all, and this is a key feature for us to be able to verify the shape of a state object. Also, the problem @STRML mentioned with dynamically assigning to properties when
[key: string]: voidis specified doesn't seem to have anything to do with optional or nullable properties, anything throws that error. See https://gist.github.com/jrajav/90400fe4394f4064ec725ae0aec4f75c