環境
flow-bin: 0.78.0
Issues
https://github.com/facebook/flow/issues/4946
https://github.com/facebook/flow/issues/2626
結論
Exact Types {||}
を object type spread {...}
を使って通常のタイプに戻し, そうでないタイプと Intersection Types &
で結合する
つまり、こう
type A = { a: number };
type B = {| b: string |};
const intersection: A & { ...B } = {
a: 1,
b: 'hoge'
};
この方法でいくつでも連結させられる
追記
2018/08/08
上記の方法で別のエラーに遭遇した
./src/components/Contents.jsx: Cannot read property 'forEach' of undefined
.babelrc から babel-plugin-flow-react-proptypes
を取り除くとエラーが出ないことから、原因はそのプラグインにあると予想される。よって自分で PropTypes を書いている人は問題ない
どうやらこの方法は libdef (flow-typed 等) から import した type には使えない らしい
https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/106
この babel plugin がソースコードをトランスパイルするタイミングで libdef の型定義にアクセスして等価な PropTypes を生成しなきゃいけないけど、そのためには libdef の .js.flow もパースしないといけないよね、ということみたい
まだ Issue は Open なので、ひとまず対処療法を考えることにする
解決策
- libdef に書いてある type definition をソースコードにコピペする(
-
'no babel-plugin-flow-react-proptypes';
で自動変換をやめて、そのファイルの prop-types は自分で書く - あきらめて
any
にする
対象が少ないなら 1 で良いと思う。しかし、そもそも Flow を使いながら PropTypes も必要なケースは限られる(モジュールとして公開しつつ、Flow のない環境でも利用される場合)ので、 本当にそのプラグインが必要なのか考えた方がいい
追記ここまで
ダメな例
素直に A & B
すると?
type A = { a: number };
type B = {| b: string |};
const intersection: A & B = {
a: 1,
b: 'hoge'
};
3行目で Flow エラー(一部抜粋)
[flow] Cannot assign object literal to
intersection
because propertya
is missing inB
[1] but exists in object literal [2]. (References: [1] [2])
なんでダメなの?
-
B
は Exact Types なので、b
以外のプロパティを 持っていてはならない -
A
はa
というプロパティを持っている -
A & B
は「b
しか持っていないが、a
も持っているタイプ」という矛盾を孕んでいる- タイプを作った時点ではエラーにならない
- そのタイプで作られた変数は、何を代入してもエラーになる
このことはドキュメントにも明記されている
Intersections of exact object types may not work as you expect. If you need to combine exact object types, use object type spread:
しかしドキュメントで紹介されているのは Exact Types 同士の結合 だけで、そうでないものが混ざったケースについては(多分)載っていない
{ ...A, ...B }
すると?
上記のドキュメント通りにやってみる
type A = { a: number };
type B = {| b: string |};
const intersection: { ...A, ...B } = {
a: 1,
b: 'hoge'
};
エラーは起きない
しかし、 intersection.a
のタイプは void | number
になる
つまり、 a
が含まれていなくてもエラーが起こらない
type A = { a: number };
type B = {| b: string |};
const intersection: { ...A, ...B } = {
b: 'hoge'
};
a
を number
として扱いたい場合は if (intersection.a) { ... }
と書く必要がある
これは大変面倒
なんでダメなの?
本来の object type spread は、コピー可能なプロパティだけを shallow copy するもの。それに倣って、Flow は、プロパティが存在するとしてもコピー可能かどうかは分からないから、全てのプロパティをOptional object propertyies ?
という扱いにするのだろう(多分)
そういう仕様としか言えない
所感
react-router-dom
の ContextRouter
を Props
に結合するときに使った。 ContextRouter
は Exact Types なので、そのまま結合するとエラーが起きてしまう
しかし、そもそも HOC の injection props を Exact Types にする意味って何かあるのだろうか。非常に使いづらいのだが。たぶん .d.ts から flow-typed を作った人がそうしたんだろうけど
$Exact<T>
の逆の操作は { ...T }
って覚えておけばいいと思う