最初に知るべきこととして、Flow も TS も型システムのセマンティクスがよく似ている。
Redux & Typescript typed Actions with less keystrokes の 記事のかなり魔術的なコード、実は flow でも ts でもそのまま動いたりする。
自分は両方頻繁に使うので(flow寄りだが)、どういうコードを書くと手戻りが少ないか、考えながらコードを書いてるか書き出してみる。
お断り
flowtype と typescript の、特に typescript 的なベストプラクティスに反する可能性がある。
完全にコンパチにするのは不可能だが、極力似たイディオムを使う。
どれも「極力頑張る」という感じで原則禁止というわけではない。大事なのは相互にコードを持ち出す時のポータビリティ。最悪なのは型がないという状態。
あと自分は TypeScript の非標準なモジュール拡張を非常に MS 的な所作だという理由で嫌っている自覚があるので、その辺の温度感も差し引いてもらいたい。
TypeScript: strictNullChecks を有効にする
{
"compilerOptions": {
"strictNullChecks": true,
"strictFunctionTypes": true,
"alwaysStrict": true
}
}
こっちのほうが flow の挙動に近い。
TypeScript: internal-module を使わない
// NG
module MyInternalModule.Helpers {
export class MyClass {}
}
// OK
// helpers/MyClass.ts
export class MyClass {}
ES Modules を 使えば不要なはず。
tslint なら no-internal-module: true
と no-var-require: true
TypeScript: namespace を使わない
コードは略。 internal module と同様、 ES Modules にそんなものはない。
非標準なモジュールシステムを使わないという点で一貫する方が良い。
tslint なら no-namespace: true
TypeScript: private/public を使わない
// NG
class MyClass {
private foo: string = 'FOO'
}
// OK
class MyClass {
foo: string = 'FOO'
}
JSにそんなものはないし、 むしろ private field は #foo
になりそう。
tc39/proposal-private-fields: A Private Fields Proposal for ECMAScript
stage3 に入って、 typescript が対応したら考える。
そもそも、ここしばらく React の componentDidMount 以外で目的で class を使った記憶がない。 最近はかなりFP厨気味なコードを書いている自覚があり、 class もオブジェクトへの副作用もそもそも不要だと思ってコードを書いている。
TypeScript: enum を使わない
// NG
enum Color { RED = 'red', GREEN = 'green', BLUE = 'blue' }
// OK
type Color = 'red' | 'green' | 'blue'
union type で十分。
探したが tslint に no-enum
なかった。誰もそんな発想をしていないっぽい。
FlowType: nullable syntax を使わない
// NG
let nullableValue: ?string = null
// OK
let nullableValue: string | null = null
この表現の差として下の方では undefined が弾かれてしまうが、これとは別に、自分は明示的に空である値 を代入する際は null を使うというルールを採用しているので、問題ない。
FlowType: TypeScript 共通: 組み込み型をできるだけ使わない
$Keys<T>
とか keyof<T>
とか使い込むと持ち出せなくなる。
これらをヘビーに使うコードはライブラリ型定義だと思われるので、そういう場合はコードの共通化を諦めて両方書いて flow-typed
や @types
の仕組みに乗せる。
どうあがいても無理な import type と nested module
Flow は 型情報はコンパイル時に完全に消去され、インスタンスと型が明確に区別されている。主にこれが理由で enum がない。
import type {...} from '...'
または import {type ...} from '...'
を TypeScript で表現する方法はない。
また、逆に TypeScript の型定義で使われる表現で、次のようなものはFlowで上手く表現する方法がない。
module A {
module B {
export class C {...}
}
}
これは flow の 型定義構文の module が入れ子にならないので、一旦複数の module に分解し直さないと同等の表現ができない。昔これが理由で flow への .d.ts
変換器を書くのを諦めた。
おまけ: 両者の Pros/Cons
Flow Pros: 導入コストが本当に低い
Flow Cons: flow server の プロセスが頻繁に行方不明になる / 追っていこうにも ocaml でソースが辛い
TS Pros: IDEが安定している
TS Cons: 非標準な割れ窓が沢山ある / スクラッチ以外の導入が困難