Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Vueで「見た目」「振る舞い」を分離してみよう
Search
Nkowne63
September 21, 2019
0
560
Vueで「見た目」「振る舞い」を分離してみよう
Nkowne63
September 21, 2019
Tweet
Share
More Decks by Nkowne63
See All by Nkowne63
TypeScriptのコード生成をつらくしないために
neutron63zf
1
610
2020-11-05-side-effects-composition__1_.pdf
neutron63zf
1
400
vueで中規模以上のフロントエンドを組んでいて 役に立ったtips
neutron63zf
5
3.1k
20200128_nkowne63
neutron63zf
0
32
20190523_nkowne63zf_1.pdf
neutron63zf
0
390
「つなぎこみ」を自動化する
neutron63zf
0
460
for文禁止縛り in JS
neutron63zf
0
680
Featured
See All Featured
BBQ
matthewcrist
85
9.3k
Intergalactic Javascript Robots from Outer Space
tanoku
269
27k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
410
Typedesign – Prime Four
hannesfritz
40
2.4k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
Music & Morning Musume
bryan
46
6.2k
Docker and Python
trallard
40
3.1k
Put a Button on it: Removing Barriers to Going Fast.
kastner
59
3.5k
Keith and Marios Guide to Fast Websites
keithpitt
409
22k
Art, The Web, and Tiny UX
lynnandtonic
297
20k
Agile that works and the tools we love
rasmusluckow
327
21k
Teambox: Starting and Learning
jrom
133
8.8k
Transcript
Vueで 「見た目」 「状態 & 振る舞い」 を分離してみよう
自己紹介 張 たいよ (GitHub: @neutron63zf) 東京大学理学部物理学科 4年 • ventus-inc ◦
JavaScript / TypeScript ◦ Vue.js / Nuxt.js • (元)東京大学五月祭常任委員会 ◦ AWS / Nginx / Docker ◦ Node.js ( Express )
作っているサービス(whooop!) • スポーツチームの発行したカード を買うことで応援できる • カードを買うことでイベントなどの さまざまな特典が得られる • オークションなどで、ユーザー間 でデータをやりとりできる
概要 • コードの肥大化の例 • なぜコードが肥大化するのか? • 「見た目」と「状態&振る舞い」の分離 • 余談 •
まとめ
コード肥大化の例
最初のコード template部分 script部分 • template部分は表示してるだけ • scriptでは、created(表示する前) にてAPIからユーザーを取得 • 取得できたら表示
• 24行
よくある要望(その1) • ローディング中は別の内容を表示したい • 取得でエラーが発生した場合も別の内容を表示したい
よくある要望(その1) • 状態と条件分岐を追加 • コードは一気に増えた • でもまだ全然シンプル
よくある要望(その2) • 今度は「ユーザーのフォロー一覧」をつくることになった • フォロー一覧を取得するにはユーザーの nameではなくidが必要 → • ユーザーを取得してからユーザーのフォロー一覧を取得する •
当然それについてもローディング・エラー時の表示を付け加えたい
よくある要望(その2) • 状態と処理を追加
よくある要望(その2) • 表示の場合分けも追加 ↓ • 「ローディングとエラーの表 示」 • 表示するデータを一つふや す
これだけで、25行だったファイルが 58行まで増えてしまった 処理もちょっと読みづらくなる 本質的でない部分も多い
なぜコードが肥大化す るのか?
仮説(1):コンポーネントを切り出さなかったからでは? コンポーネントの切り出しを行うと、確かにコードは見 やすくなる。 だが、loadingの処理などはいづれにせよ毎回書くこと になってしまう。 それも、ほとんど同じコードを毎回書くことになる。 (切り出し続けたプロジェクトの末路)
仮説(2):ロジックをまとめてないからじゃない? vueのcomposition-apiを使うと「this.loadingを falseにする」といった処理などを切り出せるの で、圧倒的にコンポーネント内のコードは短くな る。 しかし、それでもtemplate部分では何回も場合 分けが必要で、それは毎回書かなければいけな い。 composition-apiによる先程のと等価なコードの例
ではどうしてコードが肥大化するのか? • 先程の解決策たちは、確かに一定の効果があったが、抜本的な解決( =この場合だと、loadingに まつわる処理を毎回書かずに済む)にはならなかった。 ◦ 1つ目では、コンポーネントを切り出すことで、 「templateでの条件分岐」を切り出せたが、 「loading,errorの管理」は毎回書かないといけなくなった。 (なんなら前者もあまり解決できていない)
◦ 2つ目では、「loading,errorの管理」は楽になったが、「 templateでの条件分岐」は毎回書か ないといけない。 • どうも「loading、 error」にまつわる「loading,errorの管理」(状態)と「templateでの条件分岐」(振 る舞い)は一緒に切り出さないといけないらしい。 • →「状態 & 振る舞い」と「見た目」を分離したい。
「見た目」と 「状態&振る舞い」の 分離
「見た目」だけのコンポーネントは簡単 • いわゆる「stateless」なコンポーネント • vueで言うと、propsとcomputedしかない。 • イベントは「this.$emit」で親コンポーネント に処理を委譲する vueの公式docsから
「状態&振る舞い」だけのコンポーネント...? • ちょっとわかりづらいと思うので実際に例を見てみる • loadingやerrorではなく、promiseにまつわる状態管理を楽にしてくれるライブラリに 「Vue Promised」( https://github.com/posva/vue-promised )というものがある •
今回の例をVue Promisedで書き直してみると次のようになる
Vue Promised(1) script • propsとcomputedだけになっている。 • つまり、「stateless」になっている • コードも圧倒的に短い (公式のサンプルでは
dataに代入していますが、 statelessであることを強調する ためにcomputedに入れています。一応、 this.nameが変わると再計算される。)
Vue Promised(2) template • v-ifが消滅しているかわりに、 「Promised」というコンポーネントの slots に表示を入れている • promiseが解決されていないときは
pending、拒絶されたらrejectedのス ロットが自動で表示される • 58行だったのが37行に
Vue Promised(3) templateは変わってない...? • 表示はそんなに変わっていないように見える。 v-if消えただけ...? • だが、「v-if」が消滅したことによる効果は実は結構大きい ◦ v-ifの記述順番を気にしなくても済む
◦ たくさんあるv-ifの意味をいちいち考える必要がなくなる ▪ つまり、「ここのv-ifは要するにどういうことなんだ」ということが激減する • loading,errorの状態管理と、それによる表示の場合分けを自分で毎回実装する必要がなくなった
Scoped Slots(1) カウントアップ • これはどう実装しているのか?他のパターンを自分で抜き出したい場合はどうするのか? • 実はこれは「Scoped Slots」を使うことで簡単に実装できる • 「1秒ごとに数字を1加算していく」という振る舞いを実装した「
CountUp」コンポーネントを作ってみる
Scoped Slots(2) 素直な実装 • CountUpを素直に実装するとこうなる • countをゼロから毎秒インクリメントしていくだけ • 上のcountの部分の表示は自由に変えられるようにしたい。
Scoped Slots(3) 表示の切り出し • 表示を親側から差し込むなら「 Slot」という仕組みがあるので それを使う。 CountUp.vueのtemplate部分
Scoped Slots(4) 単純なslotだと... • これだけだと、右のように書くと、「 CountUpのcount」ではな く「Slotを埋めた側のcount」が表示されてしまう • これは、普通、親側で先に slotの中身を計算しているため
• これだと正しく実装できていないので、さっき作った「 Slot」を 「Scoped Slot」に書き直す (うまく行かない例)
Scoped Slots(5) Scoped Slotへの書き直し • CountUpコンポーネントで書き足す必要があるのは「 Slotに 通常のコンポーネントにするようにプロパティーを渡す」部分 だけ (CountUp側の書き直し)
Scoped Slots(6) Scoped Slotを使う • CountUpコンポーネントを使う側では v-slotを指定する必要がある • v-slotで指定したオブジェクトのプロパティーに、 CountUpコンポーネントのデータが
入っている • 実際に表示すると、毎秒カウントが増えて いるのがわかる • CountUpが行っているのはカウントだけで、 表示は使う側が自由に付け替え可能(「見た目」と「状態 &振る舞い」の分離) (使う側の書き直し)
余談
より細かく考えると... • Scoped Slotsで分離できるのは「状態 & 振る舞い」であるが、「状態のない見た目以外 の全部」ということもできる • 大体のVueのアプリケーションは以下の 4つの部分に分割できると考えられる
Scoped Slotsは下の3つをまとめていると考えることができる ◦ 状態のない見た目 ◦ 状態と紐付いた見た目 ◦ 状態とそれに対する操作 ◦ 純粋で副作用のない関数
使い分けとさらなる分割 Scoped Slotsは以下の3つをまとめている存在であると考えることができる • 状態と紐付いた見た目 • 状態とそれに対する操作 • 純粋で副作用のない関数 これらのうち、Scoped
Slotsでしか実装できないのは一番上だけ! 「純粋で副作用のない関数」は自明に切り出せる 「状態とそれに対する操作」は「 Vuex」や「composition-api」で切り出せる Scoped Slotsを実装するときには一番上であるかを考えよう。 既に実装したScoped Slotsも分割ができる。
まとめ
まとめ • loading や error をはじめ、「状態に関わる表示」のコードは増殖しがち • slot を作り、それに値を渡す( scoped-slots)ことで「状態に関わらない表示」と「状態に
関わる表示」を分離できる