GA technologiesの澤田です。先日友人に「Vueとpugで何か記事を書きたい」って話をしたら
友人「そういやpugのテンプレート構文で書ける事はだいたいVueの記法で書けるよ。」
との言葉を貰いました。
今までVueにおけるpugは単なる構文的に用いていて、pug独自のテンプレート構文をVue内で利用したことはありませんでした。
そこでpugにはどのような構文があって、そしてそれをVueで使う意味があるのか・ないのかを個人的に調べた結果をまとめてみました!
はじめに
前提知識
- Vue.jsの開発経験
- pugのふわっとした理解
Vue.js普段書いていて、さらにpugを 使っている / 使おうとしている フロントエンドエンジニア向けの記事になります。
pugの構文をVueの構文に置き換えた上で、どっちを使うべきか?ということをつらつらと考察していきます。
そもそもpugとは
pugは短い記法でHTMLを記述出来るJST(JavaScript Template)の一つです。とてもスッキリ記述出来るため、全体の構造を理解しやすい・構造の変更が容易、などのメリットがあります。
.wrapper input#checkbox-id( type="checkbox" checked) label(for="checkbox-id") ラベル
↓
<div class="wrapper"> <input id="checkbox-id" type="checkbox" checked="checked"> <label for="checkbox-id">ラベル</label> </div>
Vueでpugを使うこと自体のメリット・デメリットも当然ありますが、ここでは割愛します。
それでは本題に入ります。以降はpugとVueの記述がないまぜになるため、区別のために語頭にpugと付けて pug変数
などと記述することがあります。(対してVueで使用するJavaScript変数はJS変数と記述します)
pug変数ではなくJS変数を使う
pugではpug変数を使用できますが、template内でのみ有効でscriptからは参照できません。順当にJS変数で記述するべきでしょう。
- var url = 'path/to/page' a(href='/' + url) Link
↓
<template lang="pug"> a(href="`/${url}`") Link </template> <script> export default { data: () => ({ url: 'path/to/page' }) } </script>
明確にtemplate内でしか使用しない文字列ならpug変数でもいいかな?とも思いましたが、例えばi18n対応をする際には結局JavaScriptになることなどを考えると最初から使用しない方が良いという結論です。
pugの&attributesではなくv-bindを使う
&attributesはオブジェクト形式のpug変数をattributesに展開してくれる構文です。
例えば type
, pattern
などのセットを変数で書いておいて、まとめてattributesに指定するといった使い方ができます。
便利な構文ですが、Vueの場合はv-bindで同様のことができます
div(data-bar="bar")&attributes({'data-foo': 'foo'})
↓
<template lang="pug"> div(data-bar="bar" v-bind="attrs") </template> <script> export default { data: () => ({ attrs: { 'data-foo': 'foo' } }) } </script>
pugの制御構文ではなくディレクティブを使う
制御構文とはつまりfor, each, if/unless/else, case/whenのことです。
pugのこれらの制御構文で用いる条件式ですが、残念なことにJS変数を使用することはできません(当然といえば当然ですが)。
つまり静的なhtmlに展開される訳であって、決してVueのリアクティブな構文に変換される訳ではないのです。
制御構文はVueのディレクティブが用意されているのでこちらを使うべきでしょう。以下はpugにおけるcase/whenをVueに置けるv-if/v-else-if/v-elseに置き換える例です
- var status = 'hoge' //- あくまでpug変数 case status when 'hoge': p hoge! when 'fuga': p fuga! default: p default!
↓
<template lang="pug"> template(v-if="status === 'hoge'"): p hoge! template(v-else-if="status === 'fuga'"): p fuga! template(v-else): p default! </template> <script> export default { data: () => ({ status: "hoge" }) } </script>
リアクティブにする必要のない繰り返しなども当然あるでしょうが、記述の統一という観点からもVueのディレクティブを用いるべきでしょう。
ところでVueにはswitch/caseのような制御ディレクティブはありません。過去にPullRequestが出たこともあるみたいですが、closeされています。
もしもwhen/caseを使いたいような複雑な分岐に直面した場合は、リンク先の議論にある通りv-if/v-else-if/v-elseをtemplateの中で何個も繋げるよりも、computedを用いるなどしてリファクタリングを行うべきです。
pugのincludeではなく単一ファイルコンポーネントを使う
指定した.pugファイルの内容をインライン展開します。使う目的としては2つ考えられます
- 複数のファイルから利用するため別ファイルに記述する
- header/body/footerなど構造的な意味で分割する
どちらの場合もVueのコンポーネントを使うべきでしょう。
というのも、残念なことに私の知る限りのテキストエディタでは.pugファイルにおけるVueのシンタックスハイライトが効きません。
もしscriptやstyleを使う必要がない場合は関数型コンポーネントにすることもできます。
<template lang="pug"> .modal include path/to/modal-header include path/to/modal-body include path/to/modal-footer </template>
↓
<template lang="pug"> .modal modal-header(v-bind="someProps") modal-body(...) modal-footer(...) </template> <script> import ModalHeader from 'path/to/ModalHeader' import ModalBody from 'path/to/ModalBody' import ModalFooter from 'path/to/ModalFooter' export default { components: { ModalHeader, ModalBody, ModalFooter } } </script>
propsの受け渡しに関する記述を書くのは多少煩わしいかもしれませんが、各部分のコンポーネント単体での見通しが良くなるためこれは必要なことと考えます。
例えば上の2.のようにheader/body/footerなどの構造的なファイル分割を行ったとすると、分割先の.pugファイルではそのファイル内では定義されていない親コンポーネントのコンテキストを記述することになります。これはpugの利点でもあり、同時にコードがわかりずらくなる原因でもあります。あまり使わない方が良いでしょう。
pugのTemplate Inheritanceではなくslotを使う
Vueで言うslotのようなことがpugでもできます。逆に言えば、slotで出来ることをわざわざpugでする必要も無いでしょう。
<template lang="pug"> extends path/to/modal.pug block header h1 タイトル block body div ここはbodyです </template>
↓
<template lang="pug"> modal template(v-slot:header) h1 タイトル template(v-slot:body) div ここはbodyです </template> <script> import modal from 'path/to/Modal' export default { components: { Modal } } </script>
pugのmixinをVueで使うべきか否か
最後になりますがpugのmixinです。実を言うとこの構文だけは上手く使えば記述が楽になるのではないかと考えています。
と言うのも、Vueにおいてコンポーネントにpropsを渡すのはpugと同様にできますが、ディレクティブは別コンポーネントのelementに紐付けることは通常できないからです。以下は「引数に渡したプロパティをv-modelに引き渡すmixin」をVueのコンポーネントに置き換える例です。
<template lang="pug"> mixin input-number(prop) input(type="number" v-model.number=prop)&attributes(attributes) div +input-number("hoge") +input-number("fuga")(required) </template> <script> export default { data: () => ({ hoge: 100, fuga: 200 }) } </script>
↓
<template lang="pug"> div input-number(v-model="hoge") input-number(v-model="fuga" required) </template> <script> import InputNumber from 'path/to/InputNumber' export default { components: { InputNumber }, data: () => ({ hoge: 100, fuga: 200 }) } </script>
InputNumber.vue
<template lang="pug"> input(type="number" v-bind="$attrs" :value="value" @input="v => $emit('input', +v)") </template> <script> export default { props: { value: { type: [String, Number], required: true } } } </script>
v-model
ディレクティブは実際には :value
propと @input
ハンドラーを追加するシンタックスシュガーです。そのためそれを受け取るコンポーネント側では単なる「変数名の置き換え」では済まない程度の記述が求められます。
さらに言うとv-modelは上記のようにすれば一応解決できますが、全てのディレクティブの操作を別コンポーネントのelementに上手く割り当てることは不可能です。
ただ、includeの時にも書きましたがシンタックスハイライトが効かないなどの問題があるため、やはりオススメはできません。
おわりに
もし正確じゃない記述があったり、もっといい方法があるよ!って時はコメントいただけると助かります。
あれこれ否定的な考察を述べましたが私はpugが大好きです。できることならpugの便利な構文がうまい形でVueで利用できるようになれば嬉しいですね。