Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 - @Takepepe / Takefumi.Yoshii / DeNA DEG - 状態管理全般の話題が好き - 最近は TypeScript に夢中

Slide 3

Slide 3 text

Flux の脆さと型武装

Slide 4

Slide 4 text

Flux はいとも簡単に壊れる View / middleware の各所は State / Payload へ 強依存 している。 規模が大きくなるにつれ 依存 が分散し、 リリース後、それは予測不能に陥る。

Slide 5

Slide 5 text

Flux はいとも簡単に壊れる State / Payload スキーマをリファクタ。 影響範囲考慮から漏れた View で NaN が表示される… dispatch が空振りする…

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

型武装は 手動 or 推論? 型定義は面倒なことが多い。 import & 手動キャストは消耗の元。 指定次第で、最悪の場合エンバグする。 ※ エラー回避の any 潰し・期待型強制キャストなど ※

Slide 9

Slide 9 text

型武装は 手動 or 推論? そこで、関数導出の推論型に頼る。 推論型はキャスト不要で型が付き、 必要な型定義・指定だけが残りやすい。

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

話は変わって…

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Vuex が好き 型都合で普段は React を使っている。 でも Vuex や MobX ぐらいの 「DX」が好き。 Redux は面倒なことが多いと思う。 Redux 嫌 -> Vuex 型NG -> ??? な人いそう ※ DX (Developer Experience = 開発者体験) ※

Slide 14

Slide 14 text

コミュニティ・エコシステムは尊い。 Vuex の良いところだけを輸入し、 Redux で型強・爆速開発したい。 …こんなお気持ちのもとに作りました でも Redux で頑張る ※ サードパーティが型定義で足を引っ張ることがある ※

Slide 15

Slide 15 text

redux-aggregate npm i redux-aggregate

Slide 16

Slide 16 text

Redux の DX helper middleware ではないです DX (Developer Experience = 開発者体験)

Slide 17

Slide 17 text

そもそも Redux の何が辛いのか

Slide 18

Slide 18 text

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 の悪さ

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

redux-aggregate で +1 const state = { count: 0 } const mutations = { increment (state) { return { ...state, count: state.count++ } } } 輸入する Vuex DX の良さ

Slide 21

Slide 21 text

クリーン・FP な状態管理 状態を受けとり、変更した状態を返す「mutation関数」 状態変更に必要な関数は、本来これだけ。 もはや Redux 関係ないほどの純関数。

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

DX は合格。でも型が… 生成された ActionCreator は Payload型 を都度 import して 手動キャストするしかなかった。 そして 2018年3月…

Slide 25

Slide 25 text

TypeScript 2.8 革命

Slide 26

Slide 26 text

Type inference in conditional type ConditionalType は型推論の三項演算子。 Generics が型要件を満たしていれば、指定の型を返す。 強力なのが ConditionalType で型キャプチャを取る 「infer」

Slide 27

Slide 27 text

mutation 第2引数型(開発者が書くところ) function setCount (state: State, amount: number): State { return { ...state, count: amount } }

Slide 28

Slide 28 text

ActionCreator 第1引数型にマッピング(自動生成) function setCount (payload: number): { type: string, payload: number } { return { type: 'counter/setCount', payload } }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

全てに推論型が回る 関数一発で型付き Boilerplate が生成

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

型定義・キャストは最小限。型付けは完璧に。 新たに mutation 関数を追加すれば、 ActionType / ActionCreator は自動で追加。 mutation 関数の Payload スキーマを変更すると、 末端の View までエラーが行き渡る。 ※1. Mapped types & Lookup types / ※2. Type inference in conditional types ※2 ※1

Slide 34

Slide 34 text

テストで担保してたところ…

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

それ、2018年現在 型で全担保できるよ

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Thanks !

Slide 42

Slide 42 text

Flux 型武装ということで

Slide 43

Slide 43 text

Vuex 版もあります

Slide 44

Slide 44 text

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