Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Inferred Types for Flux

5d9cd19df0e91caac118b793b4f803d5?s=47 Takepepe
August 03, 2018

Inferred Types for Flux

まぼろしのJS勉強会 #5 「型とテスト」https://maboroshi.connpass.com/event/93959/

5d9cd19df0e91caac118b793b4f803d5?s=128

Takepepe

August 03, 2018
Tweet

Transcript

  1. Inferred Types for Flux - 推論型武装 2018 -

  2. 自己紹介 - @Takepepe / Takefumi.Yoshii / DeNA DEG - 状態管理全般の話題が好き

    - 最近は TypeScript に夢中
  3. Flux の脆さと型武装

  4. Flux はいとも簡単に壊れる View / middleware の各所は State / Payload へ

    強依存 している。 規模が大きくなるにつれ 依存 が分散し、 リリース後、それは予測不能に陥る。
  5. Flux はいとも簡単に壊れる State / Payload スキーマをリファクタ。 影響範囲考慮から漏れた View で NaN

    が表示される… dispatch が空振りする…
  6. テストで整合性担保? 整合性担保テストは属人的になりがち。 型の導入で 影響範囲 の考慮漏れを削減、 安心してリファクタが出来る。

  7. テストの守備範囲が減る 型で守れない規約は 仕様・要件 の 場合が多く、必要なテストに集中できる。 型とテストのバランスを見極める。 ※ 閾値による条件分岐 / e2e

    など ※
  8. 型武装は 手動 or 推論? 型定義は面倒なことが多い。 import & 手動キャストは消耗の元。 指定次第で、最悪の場合エンバグする。 ※

    エラー回避の any 潰し・期待型強制キャストなど ※
  9. 型武装は 手動 or 推論? そこで、関数導出の推論型に頼る。 推論型はキャスト不要で型が付き、 必要な型定義・指定だけが残りやすい。

  10. 型システムの進化 TypeScript / Flow の推論器は日々進化。 型が苦手だったライブラリも、 今後の型システム機能追加で 変貌する可能性がある。

  11. 話は変わって…

  12. 状態管理ライブラリ各位への想い

  13. Vuex が好き 型都合で普段は React を使っている。 でも Vuex や MobX ぐらいの

    「DX」が好き。 Redux は面倒なことが多いと思う。 Redux 嫌 -> Vuex 型NG -> ??? な人いそう ※ DX (Developer Experience = 開発者体験) ※
  14. コミュニティ・エコシステムは尊い。 Vuex の良いところだけを輸入し、 Redux で型強・爆速開発したい。 …こんなお気持ちのもとに作りました でも Redux で頑張る ※

    サードパーティが型定義で足を引っ張ることがある ※
  15. redux-aggregate npm i redux-aggregate

  16. Redux の DX helper middleware ではないです DX (Developer Experience =

    開発者体験)
  17. そもそも Redux の何が辛いのか

  18. Redux で +1 const initialState = { count: 0 }

    const types = { INCREMENT: 'COUNTER_INCREMENT' } const increment = () => ({ type: types.INCREMENT }) function reducer(state = initialState, action) { switch (action.type) { case types.INCREMENT: return { ...state, count: state.count++ } default: return state } } Redux DX の悪さ
  19. Vuex で +1 const state = { count: 0 }

    const mutations = { increment (state) { state.count++ } } Vuex DX の良さ
  20. redux-aggregate で +1 const state = { count: 0 }

    const mutations = { increment (state) { return { ...state, count: state.count++ } } } 輸入する Vuex DX の良さ
  21. クリーン・FP な状態管理 状態を受けとり、変更した状態を返す「mutation関数」 状態変更に必要な関数は、本来これだけ。 もはや Redux 関係ないほどの純関数。

  22. Don't commit Boilerplate. mutation 関数名を起点に Reduxに 必要なボイラープレートを生成。 src コードはいたって単純。

  23. Don't commit Boilerplate. ボイラープレート 同士の整合性は 自動生成により担保される。 この時、mutation 関数が 規約を満たしているかも検査。 ※

    State を 受け取り State を返すという規約 ※
  24. DX は合格。でも型が… 生成された ActionCreator は Payload型 を都度 import して 手動キャストするしかなかった。

    そして 2018年3月…
  25. TypeScript 2.8 革命

  26. Type inference in conditional type ConditionalType は型推論の三項演算子。 Generics が型要件を満たしていれば、指定の型を返す。 強力なのが

    ConditionalType で型キャプチャを取る 「infer」
  27. mutation 第2引数型(開発者が書くところ) function setCount (state: State, amount: number): State {

    return { ...state, count: amount } }
  28. ActionCreator 第1引数型にマッピング(自動生成) function setCount (payload: number): { type: string, payload:

    number } { return { type: 'counter/setCount', payload } }
  29. type A1<T> = T extends (a1: infer I, ...rest: any[])

    => any ? I : never type A2<T> = T extends (a1: any, a2: infer I, ...rest: any[]) => any ? I : never type MT<T> = (state: A1<T>) => A1<T> type MTPL<T> = (state: A1<T>, payload: A2<T>) => A1<T> type CR<T> = () => { type: string } type CRPL<T> = (payload: A2<T>) => { type: string; payload: A2<T> } type Mutation<T> = MT<T> | MTPL<T> type Creator<T> = T extends MT<T> ? CR<T> : CRPL<T> 推論マッピング型(redux-aggregate 抜粋)
  30. type A1<T> = T extends (a1: infer I, ...rest: any[])

    => any ? I : never type A2<T> = T extends (a1: any, a2: infer I, ...rest: any[]) => any ? I : never type MT<T> = (state: A1<T>) => A1<T> type MTPL<T> = (state: A1<T>, payload: A2<T>) => A1<T> type CR<T> = () => { type: string } type CRPL<T> = (payload: A2<T>) => { type: string; payload: A2<T> } type Mutation<T> = MT<T> | MTPL<T> // 入力型を制限する type Creator<T> = T extends MT<T> ? CR<T> : CRPL<T> // 出力型のアサーションに利用 推論マッピング型(redux-aggregate 抜粋)
  31. 全てに推論型が回る 関数一発で型付き Boilerplate が生成

  32. DEMO State / Payload を壊して危機を知る https://github.com/takefumi-yoshii/redux-aggregate-example

  33. 型定義・キャストは最小限。型付けは完璧に。 新たに mutation 関数を追加すれば、 ActionType / ActionCreator は自動で追加。 mutation 関数の

    Payload スキーマを変更すると、 末端の View までエラーが行き渡る。 ※1. Mapped types & Lookup types / ※2. Type inference in conditional types ※2 ※1
  34. テストで担保してたところ…

  35. None
  36. それ、2018年現在 型で全担保できるよ

  37. DX の追求 = 必要なコードが残る

  38. 推論型 = 必要なキャストが残る

  39. 型強い = 必要なテストが残る

  40. 必要なものだけ書こう redux-aggregate  

  41. Thanks !

  42. Flux 型武装ということで

  43. Vuex 版もあります

  44. npm i vuex-aggregate Vuex の Payload を推論型で守る vuex-aggregate で map系ヘルパーも守る