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
630
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
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Optimising Largest Contentful Paint
csswizardry
33
3k
RailsConf 2023
tenderlove
29
940
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
A Modern Web Designer's Workflow
chriscoyier
693
190k
Designing on Purpose - Digital PM Summit 2013
jponch
116
7k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
2
170
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Imperfection Machines: The Place of Print at Facebook
scottboms
266
13k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
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)ことで「状態に関わらない表示」と「状態に
関わる表示」を分離できる