LoginSignup
3
1

More than 5 years have passed since last update.

Exact Types を & で結合する方法

Last updated at Posted at 2018-08-08

環境

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 & で結合する

つまり、こう

code.js
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 なので、ひとまず対処療法を考えることにする

解決策

  1. libdef に書いてある type definition をソースコードにコピペする(
  2. 'no babel-plugin-flow-react-proptypes'; で自動変換をやめて、そのファイルの prop-types は自分で書く
  3. あきらめて any にする

対象が少ないなら 1 で良いと思う。しかし、そもそも Flow を使いながら PropTypes も必要なケースは限られる(モジュールとして公開しつつ、Flow のない環境でも利用される場合)ので、 本当にそのプラグインが必要なのか考えた方がいい

追記ここまで


ダメな例

素直に A & B すると?

code.js
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 property a is missing in B [1] but exists in object literal [2]. (References: [1] [2])

なんでダメなの?

  • B は Exact Types なので、b 以外のプロパティを 持っていてはならない
  • Aa というプロパティを持っている
  • 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 } すると?

上記のドキュメント通りにやってみる

code.js
type A = { a: number };
type B = {| b: string |};
const intersection: { ...A, ...B } = {
  a: 1,
  b: 'hoge'
};

エラーは起きない
しかし、 intersection.a のタイプは void | number になる

つまり、 a が含まれていなくてもエラーが起こらない

code.js
type A = { a: number };
type B = {| b: string |};
const intersection: { ...A, ...B } = {
  b: 'hoge'
};

anumber として扱いたい場合は if (intersection.a) { ... } と書く必要がある
これは大変面倒

なんでダメなの?

本来の object type spread は、コピー可能なプロパティだけを shallow copy するもの。それに倣って、Flow は、プロパティが存在するとしてもコピー可能かどうかは分からないから、全てのプロパティをOptional object propertyies ? という扱いにするのだろう(多分)

そういう仕様としか言えない

所感

react-router-domContextRouterProps に結合するときに使った。 ContextRouter は Exact Types なので、そのまま結合するとエラーが起きてしまう

しかし、そもそも HOC の injection props を Exact Types にする意味って何かあるのだろうか。非常に使いづらいのだが。たぶん .d.ts から flow-typed を作った人がそうしたんだろうけど

$Exact<T> の逆の操作は { ...T } って覚えておけばいいと思う

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1