TL;DR
MSTのモデル定義を下記のように書き換えましょう。
export const ModelA = types.model("ModelA", {/**/})
const model = types.model("ModelA", {/**/})
type ModelA_InferredType = typeof model
export interface ModelA_ModelType extends ModelA_InferredType {}
export const ModelA: ModelA_ModelType = model
モデルの型を型推論そのままをexportせずに、きちんと名前がついた型としてexportするのがポイントです。
(参考: https://github.com/mobxjs/mobx-state-tree/issues/1425)
なぜ上記のように書き換えるのか
TypeScriptは型を推論できます。
そして、その型推論の上にmobx-state-treeのようなライブラリは成り立っているわけですが、推論された型は基本的に名前を持ちません。
たとえば、下記の ModelA
の型はなんでしょうか?
export const ModelA = types.model("ModelA", {
name: types.string
})
// 答え
IModelType<{ name: ISimpleType<string>, {}, _NotCustomized, _NotCustomized }>
では、下記の ModelB
の型はどうなっているでしょうか?
export const ModelB = types.model("ModelB", { a: ModelA })
// 答え
IModelType<{
a: IModelType<{ name: ISimpleType<string>, {}, _NotCustomized, _NotCustomized }>
}, {}, _NotCustomized, _NotCustomized >
では、下記の ModelC
の型はどうなっているでしょうか?
export const ModelC = types.model("ModelB", { a: ModelA, b: ModelB })
// 答え
IModelType<{
a: IModelType<{ name: ISimpleType<string>, {}, _NotCustomized, _NotCustomized }>;
b: IModelType<{
a: IModelType<{ name: ISimpleType<string>, {}, _NotCustomized, _NotCustomized }>
}, {}, _NotCustomized, _NotCustomized >
}, {}, _NotCustomized, _NotCustomized >
なんか辛くなってきましたね。でも、なんで辛いのでしょうか?
それは、モデルの型に名前がついていないからです。たとえば、 ModelA
の型について、いちいち a: IModelType<{ name: ISimpleType<string>, {}, _NotCustomized, _NotCustomized }>
という型が出てきて非常に煩雑になっています。
仮にこれが ModelA_ModelType
という名前だったなら、 ModelC
の型は、下記のように非常にすっきりします。
IModelType<{
a: ModelA_ModelType;
b: IModelType<{
a: ModelA_ModelType
}, {}, _NotCustomized, _NotCustomized >
}, {}, _NotCustomized, _NotCustomized >
同じことが tsc のコンパイルにも言えます。
今の tsc (3.8.3
時点) はコンパイル途中経過およびコンパイル出力結果において、 型推論結果に名前がない場合、型推論の大きな型を持ち回り、いちいちその型を展開して利用します。
MSTの場合、単純なモデルではこれは大きな問題になりませんが、複雑なネストを持つモデルを持ち始めた瞬間、上記の巨大な型をtscが用いるようになるので、コンパイル時間が簡単に膨れ上がります。
私のケースでは、ビルド時間が 1分程度 → 1時間弱にまで成長しました。また、複雑なモデルを参照しているだけの20行程度のファイルが、2000行の型ファイルにコンパイルされていました。
まとめ
MSTのようにメソッドチェーン+型推論で型を成長させていくライブラリの場合、型推論結果に名前をつけずにそのまま使っていると、いつかTypeScriptコンパイラが死にます。
(コンパイラ側で適度なところで名前をつけるなり、何とかしてほしいですが…。)
気をつけましょう。