Slide 1

Slide 1 text

Backbone.Model に 型をつけて剥がす Sub title pixiv Inc. subal 2019.9.20

Slide 2

Slide 2 text

2 誰 ● pixivFACTORY ● TypeScript と SVG と React、時々 Rails ● 型芸、SVG芸、hooks 芸の話ができます subal

Slide 3

Slide 3 text

3 型芸で負債を倒す話

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5 pixivFACTORY BOOKS

Slide 6

Slide 6 text

6 pixivFACTORY BOOKS ● 同人誌作成機能。グッズと並ぶ、 pixivFACTORY の二大柱 ● 同人誌プラン検索/入稿画面が React + Redux ● ただし、2015年に設計された React + Redux アプリケーション ● アップデートの結果表面的な記法は新しくなっている(Babel, ES6 Classes) ○ しかし本質的な設計は…? ○ それに、グッズ側は TypeScript で書くのが当たり前に変わっている

Slide 7

Slide 7 text

7 設計上の課題 ● mixin の名残がある ○ React.createClass は流石にやめているが、constructor で this を lodash/extend している ● Redux の store に Backbone.Model のインスタンスが入っている ○ Immutable.js ならまだ分かるけど、Backbone.Model です ■ まぁ私は Immutable.js も嫌いなのであったらあったで剥がしますけど ● 他にもいろいろあるが、とりあえず ↑ を何とかする。まずはそれから

Slide 8

Slide 8 text

8 とりあえずのゴール ● reducer で Backbone.Model を作らない ● store に Backbone.Model を持たない ● コンポーネントに Backbone.Model を持ち込ませない ● TS化し、型のついたただの json にする

Slide 9

Slide 9 text

9 現状

Slide 10

Slide 10 text

10

Slide 11

Slide 11 text

11 セッターで型をキャストする 仕組み ( ActiveModel::Attributes みたいなやつ ) モデルのデフォルト値

Slide 12

Slide 12 text

12 プロパティとは別にメソッドっぽいのが生えるケースがある

Slide 13

Slide 13 text

13 たとえばここが、number と勝手に推論される状況を作りたい

Slide 14

Slide 14 text

14 おっとこんなところにデ フォルト値が

Slide 15

Slide 15 text

15 extend() の引数内の defaults から、 返り値の型を推論する

Slide 16

Slide 16 text

16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

19

Slide 20

Slide 20 text

20 1個ずつ解説します みたいな型定義で倒せるのですが…

Slide 21

Slide 21 text

21 Model のインスタンスの型を考える ● Backbone.Model のプロパティは(厳密には違うけど)だいたい keyof defaults ● 仮に、extend() の引数を option とするとき… ○ defaults の型は typeof option['defaults'] ○ これを仮に P と置く ○ すると、インスタンスの型はそれをジェネリクスに受け取る TypedModelInstance

と置くことができる ○ その型は get(key: K): K[P] というメソッドを持つはず

Slide 22

Slide 22 text

22 これにより、model.get('hoge') で hoge が defaults に書かれてないキーならば、 コンパイルエラーになる

Slide 23

Slide 23 text

23 Model のインスタンスの型を考える 2 ● Backbone.Model の set は特殊で、以下の二つの記法が許容される (うげぇ…) ○ model.set('page_size', 12) ○ model.set({ page_size: 12 }) ● TypeScript にはメソッドの型をオーバーロードする仕組みがあるので、両対応で書くことは 可能

Slide 24

Slide 24 text

24

Slide 25

Slide 25 text

25 Model のインスタンスの型を考える 3 ● toJSON の型は、少なくとも推論されたすべてのプロパティを含む型を返すはず ○ さっきの言葉で言えば、とりあえず P あるいは P を継承した型であるはず ○ 実は、Backbone.Model はプロパティとは別に getters という仕組みがあり、本当は それらも toJSON に含まれる ■ が、一旦無視してすすめる

Slide 26

Slide 26 text

26

Slide 27

Slide 27 text

27 クラスの型を考える ● Backbone.Model.extend() は「クラスを作る関数」 ○ なので、返り値がクラス(あるいは newable なオブジェクト)と考える必要がある ○ TypeScript は、任意の型に対して、それを new で呼んだときの返り値を定義するこ とができる ○ Book クラスは ReturnType のような方法 で推論されるし、インスタンスの型は new の型定義から推論される

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

29 ここまでやると、 コンパイラは Book という「クラス」がどうい う構造かを理解し始める

Slide 30

Slide 30 text

30 Reducer から剥がしていく ● model クラスをやめてただの JSON にしたあとで、返り値の型が変わらないことを保証した い ○ Jest のスナップショットテストを使う ● 剥がした後に残る JSON の型を、interface State として書いておきたい ○ まぁ infer を使って出てきた結果をコピーするとか … ○ 推論結果を参考に頑張って書くしかない( optional なキーとか推論しきれてない箇所 はあるので )

Slide 31

Slide 31 text

31 テストのコツ ● もともと、backbone.model を生で使ったテストは書かれていたが、これは使わないことに した ○ 剥がした後も有効であり続けるテストでないと意味がない ○ toJSON した結果をスナップショットテストして、 ■ それが剥がした後も通る ■ あるいは、落ちるけど差分が意図したものであることを検証したい

Slide 32

Slide 32 text

32

Slide 33

Slide 33 text

33

Slide 34

Slide 34 text

34 ● props にまで Backbone のモデルインスタンスが渡っている箇所がある ● コンポーネントの外に追い出したい ● reducer にしか現れない状況を作った上で、reducer を倒すみたいな感じにしたい コンポーネントに露出している model

Slide 35

Slide 35 text

35 toJSON するだけの selector を書く

Slide 36

Slide 36 text

36 useSelector を使って末端から型付け

Slide 37

Slide 37 text

37 ● 一部の画面 / model だけしか置き換えてないのでまだまだ ● 終わったらあの型定義はちゃんと捨てる ○ 登りきった後は梯子を投げ捨てなければならない 今後

Slide 38

Slide 38 text

38 戦いはまだまだこれから…!

Slide 39

Slide 39 text

39 ● とりあえず、全部ではないが Backbone.Model を剥がしながら型付け・モダン化が進ん でいる ● TypeScript の型システムは無限に便利なのであーいうものでも型がつけられてしまう ● スナップショットテストはライブラリを消すときの強い味方 ● useSelector は最高 まとめ

Slide 40

Slide 40 text

40 ● 一つには優先度の問題 ○ 「他の改修を一切留めてやるべきか」といういつもの話 ● もう一つは、実はこっちのほうが事故らないかもという考え ○ 古くて型のないコードを「読解して」書き直すのと、古いコードの実装から型を「 推論さ せて」安全にするアプローチ ■ 前者(書き直し)のほうが良いと自信を持って言えたか?うーん … ■ 真面目に読むの嫌なのでコンパイラにやらせるは一つの方法としてある Q. Why not フルスクラッチ?

Slide 41

Slide 41 text

Backbone.Model に 型をつけて剥がす Sub title pixiv Inc. subal 2019.9.20