LoginSignup
36
21

More than 5 years have passed since last update.

Flowtype と TypeScript で両方動きそうなコードを書く

Last updated at Posted at 2018-01-23

最初に知るべきこととして、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: trueno-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: 非標準な割れ窓が沢山ある / スクラッチ以外の導入が困難

36
21
2

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
36
21