Slide 1

Slide 1 text

ひらやま Vue3 で TypeScript と slots を活用した 堅牢かつ柔軟な UI コンポーネントづくり

Slide 2

Slide 2 text

こんばんは!

Slide 3

Slide 3 text

ひらやまと申します

Slide 4

Slide 4 text

前職の人として応募したのですが、

Slide 5

Slide 5 text

退職後にこのイベントにお呼ばれされました。

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

経歴はこう

Slide 10

Slide 10 text

ただ前職はこうだった

Slide 11

Slide 11 text

結構いい感じ!

Slide 12

Slide 12 text

& Button Component の困りご & slots の活! & TypeScript の活用 Contents

Slide 13

Slide 13 text

Button Component の困りごと

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Icon Prop 付け足す?

Slide 17

Slide 17 text

1 2 3 4 5 6 7 8 < > template template > < = = = = /> Button icon icon-position icon-color text "bookmark" "left" "#ffffff" "ボタン" いろいろと受け付けることになりそう

Slide 18

Slide 18 text

はたまた…

Slide 19

Slide 19 text

1 2 3 4 5 < > template template > < = > < = /> > RouterLink to Button text RouterLink "/" "ボタン" RouterLink(NuxtLink)使いたい!

Slide 20

Slide 20 text

1 2 3 < > template template > < >{{ }} > button text button Button Component の中身

Slide 21

Slide 21 text

1 2 3 < > < > > > a button button a = ボタン href "/" Rendering された HTML

Slide 22

Slide 22 text

HTML エラー!

Slide 23

Slide 23 text

1 2 3 < > template template > < : =" ">{{ }} > component is tagName text component component を利用して、タグ名を動的にしてみる

Slide 24

Slide 24 text

1 2 3 4 5 < > template template > < = > < = = /> > RouterLink to Button tag-name text RouterLink "/" "span" "ボタン" これだったら大丈夫そう!

Slide 25

Slide 25 text

1 2 3 < > template template > < = = = /> Button tag-name href text "span" "/" "ボタン" ただ、span タグで href 使われたらHTMLエラーだ

Slide 26

Slide 26 text

タグ名ごとに受け付ける props (attrs) が異なるため ヒューマンエラーが起きやすい

Slide 27

Slide 27 text

Icon との依存関È props (attrs) の誤用問題 課題は2つ

Slide 28

Slide 28 text

Icon との依存関係 props (attrs) の誤用問題 slots TypeScript それぞれ解決!

Slide 29

Slide 29 text

slots の活用

Slide 30

Slide 30 text

そもそも slots って?

Slide 31

Slide 31 text

1 2 3 4 5 < > template template > < > < /> > div slot div MyComponent という名で以下のように定義すると…

Slide 32

Slide 32 text

1 2 3 4 5 < > template template > < > < >どんな要素でも受け付けられる > > MyComponent span span MyComponent 子要素を受け付けられるようになります

Slide 33

Slide 33 text

slots を使わないで Button Component に Icon を受け付けた場合…

Slide 34

Slide 34 text

1 2 3 4 5 6 7 < > template template > < = = = = /> Button icon icon-position icon-color text "bookmark" "left" "#ffffff" "ボタン" いろいろと受け付けることになりそう

Slide 35

Slide 35 text

slots で子要素を受け付けるようにすると…

Slide 36

Slide 36 text

1 2 3 4 5 6 < > template template > < > < /> < >ボタン > > Button BookmarkIcon span span Button

Slide 37

Slide 37 text

でもこれだと、Icon とテキストの配置用の CSS を 毎回書かないといけない

Slide 38

Slide 38 text

1 2 3 4 5 6 7 8 < > template template > < > < > < # >< /> > < # >ボタン > > > Button SymbolText template symbol BookmarkIcon template template text template SymbolText Button SymbolText という Component を作ってみました

Slide 39

Slide 39 text

1 2 3 4 5 6 7 8 < > template template > < > < > < # >< /> > < # >ボタン > > > Button SymbolText template symbol BookmarkIcon template template text template SymbolText Button slots は name を決めて入れる場所を限定できる!

Slide 40

Slide 40 text

1 2 3 4 5 6 7 8 9 10 < > template template > < = > < = > < = /> > < = > < = /> > > div class div class slot name div div class slot name div div "symbolText" "symbolText__item" "symbol" "symbolText__item" "text" slots を受け付ける SymbolText 側

Slide 41

Slide 41 text

さてここで問題

Slide 42

Slide 42 text

以下の UI の中で、SymbolText を使っているのは どこでしょう?

Slide 43

Slide 43 text

当然ここ!

Slide 44

Slide 44 text

実はここも!

Slide 45

Slide 45 text

IconText ではなく SymbolText という名にした理由がこれです

Slide 46

Slide 46 text

シンプル 1 2 3 4 5 6 7 8 < > template template > < > < > < # >※ > < # >この文章は「ポラーノの広場」... > > > Button SymbolText template symbol template template text template SymbolText Button symbol として指定するものは Icon 以外でも良い

Slide 47

Slide 47 text

slots を活用して「注入」できるようにすると 柔軟な Component の一歩につながります

Slide 48

Slide 48 text

TypeScript の活用

Slide 49

Slide 49 text

どんな課題があったか?

Slide 50

Slide 50 text

RouterLink (NuxtLink) を使うことを想定すると タグ名を柔軟に変えるようにしておきたい

Slide 51

Slide 51 text

ただ、span タグを指定しているのに、 href を誤って指定してしまう可能性が出てくる…

Slide 52

Slide 52 text

これを解決するために…

Slide 53

Slide 53 text

SpanTagButton.vue LabelTagButton.vue ButtonTagButton.vue ATagButton.vue

Slide 54

Slide 54 text

これはきつい…

Slide 55

Slide 55 text

TypeScript の Generics を活用します!

Slide 56

Slide 56 text

ここからの説明は、VSCode + Volar を 使っている前提でお話していきます

Slide 57

Slide 57 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 < > script type type type type = = { : ... } = { : ... ?: ?: } = { : ... } = { : ... } lang tagName tagName href for tagName tagName "ts" 'a' 'button' 'label' 'span' ATagProps ButtonTagProps never never LabelTagProps SpanTagProps さらに、tagName の型を、 各タグ名の文字列で固定しておく。 それぞれのタグの Props を用意し、 タグごとに受け付けたくない Props を にする。 「never | undefined 型」

Slide 58

Slide 58 text

20 21 22 23 24 25 26 27 28 29 30 31 type extends extends extends extends extends < | | | > = ? : ? : ? : ? : Props T T ATagProps T ButtonTagProps T LabelTagProps T SpanTagProps never 'a' 'button' 'label' 'span' 'a' 'button' 'label' 'span' Generics T を受け付け、T に応じて型を分岐できる

Slide 59

Slide 59 text

1 2 3 < > template template > < = /> Button tag-name "button" こうするだけで、Props が と解釈される Props<’button’> 各 Props の tagName の型が決まっているので 連鎖的に T が決まるようになっている

Slide 60

Slide 60 text

31 32 33 34 35 36 37 38 39 40 type extends => script = < >( : < > ) { : < > : {} } ({}) DefineComponentType T Props T Props T DefineComponentType new export default as 'a' | 'button' | 'label' | 'span' props $props $emit defineComponent > Props を Volar に認識させる

Slide 61

Slide 61 text

defineComponent に {} を渡しているので、 この段階ではレンダリングできません。 注意点

Slide 62

Slide 62 text

Vue2 のようにオブジェクトを正しく書いても良いですが Vue3 の setup でも上書きできます。 注意点

Slide 63

Slide 63 text

実際に利用したときの VSCode のキャプチャがこちら

Slide 64

Slide 64 text

tag-name=”button” と href を指定するとエラー

Slide 65

Slide 65 text

CI で tsc のチェックを入れれば、 リリースを防ぐこともできちゃいます

Slide 66

Slide 66 text

https://github.com/rhirayamaaan/vue-flexible-ui-components より詳しいコードを見たい方はこちら

Slide 67

Slide 67 text

@rhirayamaaan @rhirayamaaan GitHub Twitter Vue3 で TypeScript と slots を活用した 堅牢かつ柔軟な UI コンポーネントづくり