2019

6

3

jQueryからVue.jsへ移行事始めしてやんよ!!!

タグ:

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

中規模アプリを開発する上で、メンテナンス性を重要視させた上で効率化させたいなぁと思ったので jQueryからVue.jsへ移行の際の事始めを色々と。

Vue.js基本

CDNの取得

とりあえずここからCDNを取得
jQueryのCDNと同様に、htmlに貼り付けるだけでVue.jsが利用できます。

基本文法

HTML
<!-- 一度だけ変更可能なメッセージテンプレート -->
<span v-once>{{ msg }}</span>

<!-- href属性を動的に -->
<a v-bind:href="url"> ... </a>

宣言的レンダリング

{{ ○○ }}で単純なテンプレート構文を使って宣言

ディレクティブ(指令)

v-○○から始まるVueの特別な属性で、属性値に設定した式が変化したときに、DOMに適用されます。

Java Script
// Vueインスタンスの記述
var 変数名 = new Vue({ 
    // elementオプション
    el : "#要素",

    // dataコンポーネント
    data: {
        プロパティ名: 値,
        プロパティ名: 値,
    },

    // methodsコンポーネント
    methods: { プロパティ名: 値 },

    // computedコンポーネント
    computed:  { プロパティ名: 値 },

    // watchコンポーネント
    watch:  { プロパティ名: 値 },
})

基本的には変数としてVueインスタンスの宣言をして、
elには紐付けする要素。
dataに連動させる詳細なプロパティを指定させます。
この他にもmethodscomputedwatchなどがあります

Vue.jsを本格的に使う場合はコンポーネント書式などはありますが初歩的な記述は大体はeldata+アルファをまとめるだけでHTMLとJS部分は基本的なレンダリングは設定できます

主な機能(ディレクティブとオプション)

機能書式
データを表示{{ データ }} / v-text / v-html
データ(オブジェクト構造)を表示{{ $data }}
属性データの指定v-bind
入力フォームとデータの紐づけv-model
イベントとメソッドをつなぐv-on
条件表示v-if
繰り返し表示v-for
データの計算computed
データの監視watch
部品にまとめるcomponent

他にもたくさんありますが、今回はメインどころであろうディレクティブとオプションを取り上げます。

1. データの表示({{ データ }} / v-text / v-html)

HTML
<div id="app">
    <!-- ①マスタッシュタグの場合 -->
    <p>{{ myText }}</p>
   <!-- 文字列で"<h1>Hello!</h1>"が表示される -->

    <!-- ②「v-text」の場合 -->
    <p v-text="myText"></p>
    <!-- 文字列で"<h1>Hello!</h1>"が表示される -->

    <!-- ③「v-html」の場合 -->
    <p v-html="myText"></p>
    <!-- h1タグの"Hello!"で表示 -->
</div>
Java Script
new Vue({
    el: "#app",
    data: { myText: '<h1>Hello!</h1>'}
})

タグ内にデータをそのまま出力する場合は二重波括弧(マスタッシュタグ内に記述。
v-textテキストとして出力、v-htmlはhtmlとして出力。

この辺はjQueryのtext()html()と同じですね。

※マスタッシュタグはHTMLタグ中の属性などに直接記述はできません

2. 属性データの指定(v-bind)

HTML
<div id="bind" v-bind:style="{background:style}">
    <h1 v-bind:align="align">
        <a v-bind:href="href" target="_blank">
            <img v-bind:src="src" alt="ときどきWEB" />
        </a>
    </h1>
</div>
Java Script
var myBind = new Vue({
    el : "#bind",
    data: {
        href: "http://tokidoki-web.com/",
        src: "http://tokidoki-web.com/wp-content/themes/yanyo/images/logo.png",
        align: "center",
        style: "red"
    }
})

v-textディレクティブやv-htmlディレクティブだとあまりフレームワークを使ってる感は無いけど、
v-bind要素内のurlやパス属性以外にやスタイルもまるごと動的に設定できるので、こういう風にまとめられるとワンライナーで書くスクリプトと違ってテンプレート管理してるなって感じがします

省略記法

完全な構文<a v-bind:href="url"> ... </a>
省略記法<a :href="url"> ... </a>
動的引数の省略記法<a :[key]="url"> ... </a>

3. データの紐付け(v-model)

HTML
<div id="myModel">
<form name="myModelForm">
        <p>
            ①<label><input v-model="myInput" placeholder="名前を入力"></input>
            私の名前は「{{ myInput }}」です(現在の文字数{{ myInput.length }})</label>
        </p>
        <p>
            ②<label><input type="radio" v-model="myRadio" value="①">男</label>
            <label><input type="radio" v-model="myRadio" value="②">女</label>
            <br><span>性別は{{ myRadio }}</span>
        </p>
        <p>
            ③<select v-model="myOption">
                <option disabled value="">色を選択</option>
                <option value="red">赤</option>
                <option value="blue">青</option>
                <option value="green">緑</option>
                <option value="yellow">黄</option>
            </select>
            <span v-bind:style="{color: myOption}">選択した色</span>
        </p>
            
        <label><input type="checkbox" v-model="myAgree" >同意します</label>
        <p><button v-bind:disabled="myAgree==false">送信</button></p>
</form>
</div>
Java Script
var myModel = new Vue({
    el: "#myModel",
    data: {
        myInput: '',
        myRadio: '',
        myOption: '',
        myAgree: false
    }
})

v-modelディレクティブを使うとフォームなどのユーザーの入力値を即反映させることができる双方向データバインディングとして構成することができます

①では入力されたテキストを反映&その入力文字列数を反映させています

②ではradioボタンで選択したvalue値を反映させています

③ではoptionタグで選択されたvalue値を文字色としてstyleに反映させています

最後はmyAgreeキーを指定したinputタグにv-model:disabledオプションを付けることで、同じくmyAgreeキーを指定したbuttonタグと紐付けられ、チェックが付いたらbuttonタグがクリック可能になります

4. イベントとメソッドを繋ぐ(v-on)

HTML
<div id="myOn">
    <p>{{count}}回</p>
    <button v-on:click="countUp">カウントアップ</button>
    <button v-on:click="countDown">カウントダウン</button>
    <button v-bind:disabled="isClick" v-on:click="oneClick">いいね {{ good }}</button>
</div>
Java Script
var myOn = new Vue({
    el: "#myOn",
    data: { 
        count: 0,
        good: 0,
        isClick: false
    },
    methods: { 
        countUp: function(){
            this.count++;
        },
        countDown: function(){
            this.count--;
        },
        oneClick: function(){
            this.good++;
            this.isClick = true;
            alert("1いいねしました")
        }
    }
})

v-onディレクティブではクリックイベントに独自のメソッドを繋げることができます。
v-onはよく使われるディレクティブなので@で省略して書くことも可能です

dataで初期値0に設定しているcountプロパティに++--methodsプロパティに紐付ける事で「カウントアップ」「カウントダウン」をクリックすると加算・減算できます

カウントアップと同じ様に「いいね」ボタンをクッリクしたら1カウントを追加しますが、SNSのように1度しかクリックできない様に制限することもできます。

省略記法

完全な構文<a v-on:click="doSomething"> ... </a>
省略記法<a @click="doSomething"> ... </a>
動的引数の省略記法<a @[event]="doSomething"> ... </a>

5. 条件による表示(v-if)

HTML
<div id="myIf">
    <label><input type="checkbox" v-model="visible" v-on:click="show=!show">どっち?</label>
    <p>
        <button v-if="visible" v-on:click="doubt">そうだよボタン</button>
        <button v-else v-on:click="doubt">そうじゃないボタン</button>
    </p>
    <transition name="fade">
        <p v-if="show">本当にそうか・・・?</p>
    </transition>
</div>
Java Script
var myIf = new Vue({
    el: "#myIf",
    data: { 
        visible: false,
        show: false
    },
    methods: { 
        doubt: function(){
            this.show = true
        }
    }
})

v-ifディレクティブは名前通り、指定タグを条件によって表示させたり非表示させたりを設定できます

チェックボックスのON/OFFによってvisibleのtrue/falseが切り替わり、その条件によって「そうだよボタン」と「そうじゃないボタン」の表示・非表示が切り替わります

ふたたびチェックボックスのチェックを切り替えるとコメントが非表示に更新されます。

ついでにv-onと紐付けてボタンをクリックするとdoubtメソッドが発動して「本当にそうか・・・?」コメントがフェードインします。
ここではアニメーションを適用させるためにtransitionを使用しています。(下記参照)

transitionについて(余談)

transition(変化・移行)の意味を持つVue.jsの組み込み型コンポーネントのひとつで<transition>囲った要素に対してアニメーションの管理ができます。

従来のフルスクラッチでは開発ではクリック操作などでフラグ管理としてのクラスを付与して、CSSやjQueryでフェードイン・フェードアウトを付けて
変わる対象HTML要素
変える対象要素の変化(CSSまたはアニメーションスクリプト)、
切り替えるイベント管理など
外部ファイルで書いた場合はそれぞれ別々の場所の関係性を意識しなければならなかった部分<transition>を利用することで直感的にわかりやすくまとめることができます

HTML
<!-- ON/OFFのフラグ管理担当 -->
<button v-on:click="show=!show">どっち?</button>
<transition>
    <!-- transitionの対象要素&ON時の役割 -->
    <p v-if="show">本当にそうか・・・?</p>
</transition>
CSS
/* transitionがどうするかの指定 */
.v-enter-active, .v-enter-to {
    transition: opacity .5s;
}
.v-enter  {
    opacity: 0;
}

CSSの指定は初見はわかりにくいですが、Chromeの検証ツールで監視すると<transition>は見えませんが、動かすと一瞬、<transition>内に設定した要素にenter-active v-enter-toクラスが付くのが確認できます。
この操作したい対象要素のON・OFFに対する物理的変化を補ってくれてるんですね。

v-ifは単品というよりはv-modelv-onなどのディレクティブ(構成)と<transition>の様なコンポーネント(役割)と組み合わせることでユーザー操作の導線を誘導・制限する様なUI構築に能力を発揮する様です。

6. 配列やオブジェクトの表示(v-for)

HTML
<div id="myName">
    <ul>
        <li v-for="item in myArray" v-if="item % 2 == 0">{{ item }}</li>
    </ul>

    <ul>
        <li v-for="(item, key) in myName">私の名前は「{{ item.lastName }} {{ item.firstName }}」です</li>
    </ul>
    <!-- データ構造をすべて出力 -->
    <pre>{{ $data }}</pre>
</div>
Java Script
var nameList = [
    { lastName: "やんよ", firstName: "太郎" },
    { lastName: "ヤンヨ", firstName: "タロウ" },
    { lastName: "YANYO", firstName: "TARO" }
]

var myName = new Vue({
    el : "#myName",
    data: {
        myArray: [1,2,3,4,5,6],
        myName: nameList
    }
})

v-forディレクティブを使う事でマークアップの段階で配列やオブジェクトにhtmlタグから直接干渉できるようになるので、いよいよjQueryとの差別化も感じてきます。

v-forで展開対象にv-ifを組み合わせてループ中に条件一致したものだけ出力(デモでは偶数のみ出力)ということもできます。

{{ $data }}について(余談)

Vueインスタンス内で指定されたオブジェクトや配列は{{ $data }}を使う事でphpのvar_dumpの様なオブジェクト構造を全て出力できます。

<div id="app">
    <!-- データ構造をすべて出力 -->
    <pre>{{ $data }}</pre>
</div>

7. データの計算(computed)

HTML
<div id="myComputed">
    <p><input type="number" v-model.number="price">円 × <input type="number" v-model.number="count">個</p>
    <p>商品価格:{{ price }}円</p>
    <p>合計価格(税別):{{ total }}円</p>
    <p>合計価格(税込):{{ taxin }}円</p>
    <div>
        <p>{{ error() }}</p>
        <textarea v-model="comment" placeholder="コメントを入力"></textarea>
        <p v-if="!over">入力可能 <span v-bind:style="{color:color}">{{ leng }}</span> 文字({{ max }}文字制限)</p>
        <p v-else><span v-bind:style="{color:color}">現在 {{ leng }} 文字オーバーです</span>({{ max }}文字制限)</p>
    </div>
</div>
Java Script
var myComputed = new Vue({
    el: "#myComputed",
    data: { 
        price: 0,
        count: 1,
        comment: '',
        max: 30,
        over: false
    },
    methods: { 
        error: function(){
            if(this.comment.length > this.max){
                this.color = 'red'
                this.over = true
            }else{
                this.color = 'blue'
                this.over = false
            }
        }
    },
    computed: { 
        total: function(){
            return (this.price * this.count);
        },
        taxin: function(){
            return Math.round((this.price * this.count) * 1.08);
        },
        leng: function(){
            return Math.abs(this.max - this.comment.length);
        }
    }
})

computedオプションはタグ内に算出プロパティを設定できます

ひとつは入力された単品価格と個数によって合計価格と消費税8%を加算した税込価格を反映させます。

もうひとつはtextareaタグに入力された文字数のカウンター(leng)を反映させながらerrorプロパティで制限数(max)をオーバーしたらカウンター表記を条件分岐で切り替えます。

8. データの監視(watch)

HTML
<div id="myWatch">
    <p>お題:「{{ matchText }}」</p>
    <p>入力文字に「{{ forbidden }}」は{{ count }}個含まれています</p>
    <textarea v-model="watchText"></textarea>
    <p v-if="clear"><button v-on:click="startTimer">START</button>残り{{ restSec }}秒</p>
    <p v-else>入力成功!</p>
</div>

Java Script
var myWatch = new Vue({
        el: "#myWatch",
        data: {
            count: 0,
            watchText: '',
            forbidden: "ロバ",
            matchText: "ラバかロバかロバかラバか分からないので ラバとロバを比べたらロバかラバか分からなかった",
            restSec: 10,
            timerObj: null,
            clear: true
        },
        methods: {
            // カウントダウン
            startTimer: function(){
                this.timerObj = setInterval(()=> {this.restSec-- }, 1000)
            }
        },
        watch: {
            // 入力文字の正解チェック&存在チェック
            watchText: function(val){
                if(val == this.matchText){
                    this.clear = false
                    clearInterval(this.timerObj)
                }
                this.count =  (this.watchText.match(new RegExp(this.forbidden,"g")) || []).length;
            },
            // 時間切れアラート
            restSec: function(){
                if(this.restSec <= 0){
                    alert("時間切れです");
                    clearInterval(this.timerObj)
                    this.watchText = ""
                    this.restSec = 10
                }
      }
   }
})

watchオプションではデータや変数の値に変化があった場合の処理に使います

デモでは監視対象をtextareaに設定し、お題文字列に沿ったテキストを入力するとその値を監視して"ロバ"というキーワードがテキスト内に何個含まれているかが反映されます。

オマケの機能として「スタート」ボタンをクリックすると10秒間のカウントダウン処理が走り(ここはmethodsオプションで設定)時間制限内にお題の入力の成功or失敗によって分岐出力が変わります

9. 部品にまとめる(component)

Vue.jsの一番の醍醐味的な機能としてこのcomponentがあります。

コンポーネントは「名前付きの再利用可能なVueインスタンス」という説明と、よく"パーツ化する"や"機能の再利用化"といった表現がされますが、どうも理屈っぽすぎてしっくり来ない・・・

機能・効果としての"メソッド"と、名称や着想としての"コンセプト"
"悪魔合体"させた「ボクの考えた最強オリジナルタグを作る!」というイメージが個人的には一番しっくりくる表現
ですw

管理人なりの”コンポーネント化する”のイメージ

コンポーネントをボクの大好きな漫画「ダイの大冒険」で例えるならば
主人公ダイの必殺技「アバンストラッシュ」は剣を専門に使うダイが習得しましたが、師匠であるアバン先生いわく
アバン流殺法の基本を身につけた者は、剣・槍・弓・鞭・斧・拳でも発動が可能である」・・・と。

  • ①大地を斬る「大地斬」(力)の習得
  • ②海を割る「海波斬」(速度)の習得
  • ③空を裂く「空裂斬」(闘気)の習得

このアバン流殺法(力、速度、闘気の3つ)をコントロールし、三位一体となることで完成する強力な剣技である奥義オリジナルアイテム「アバンのしるし」にして作って、毎回アバン流殺法の基本をすべて習得し直さなくても
装備した人なら"誰"でも、"どんな武器"でも「アバンストラッシュ」が使えるようにしちゃうよ!って感じです。

基本書式

HTML
<div id="yanyo">
    <yanyo-component></yanyo-component>
</div>

グローバル登録の場合の書式

Java Script
Vue.component('yanyo-component', {
    template: '<h1>jQueryからVue.jsへ移行事始めしてやんよ!!!</h1>'
})
var yanyo = new Vue({
    el: "#yanyo"
})

ローカル登録の場合の書式

Java Script
var yanyoComponent = {
    template: '<h1>jQueryからVue.jsへ移行事始めしてやんよ!!!</h1>'
}

var yanyo = new Vue({
    el: "#yanyo",
    components: {
        'yanyo-component': yanyoComponent
    }
})

コンポーネントの命名規則

コンポーネント名は好きに付けられる訳でなく、既に存在する(将来定義されるかもしれない)従来のHTMLのタグ名は単語で命名されている規則性と衝突させないためにVueコンポーネント用の命名規則が存在します。

  • 「yanyo-component」・・・○ケバブケース
  • 「yanyo」、「yanyoComponent」・・・×単語やキャメルケース

(例題)投票機能コンポーネントを実装してみる

HTML
<div id="yanyo">
    <ul>
        <li v-for="member in members">{{ member }}:<vote-component></vote-component></li>
    </ul>
</div>
Java Script
Vue.component('vote-component', {
    data: function() {
        return {count:0}
    },
    methods: {
        countUp: function(){
            this.count++;
        },
        countDown: function(){
            this.count--;
        },
        reset: function(){
            this.count = 0
        }
    },
    template: `
        <div>
            <button v-on:click="countUp">{{count}}票</button>
            <button v-on:click="countDown">取り消し</button>
            <button v-on:click="reset">リセット</button>
        </div>
        `,
})

var yanyo = new Vue({
    el: "#yanyo",
    data: {
        members: ["Aさん", "Bさん", "Cさん"]
    }
})

これまでのディレクティブ機能と組み合わせてコンポーネント化するメリットの具体的なサンプルを作ります

  • v-onを使ってクリックでカウントアップするボタンを作成
  • v-onを使ってクリックでカウント1を取り消すボタンを作成
  • v-onを使ってクリックでカウントをリセットするボタンを作成
  • v-for展開するリストmembers配列を作成
  • 各投票ボタンをdivタグでまとめてtemplateに指定して
    「①追加」「②取消」「③初期化」の機能をまるごとコンポーネント化
  • ⑥最後にvote-componentと名付けたパーツ(コンポーネント)をv-for展開中に設置

たったこれだけのコード量で3人のメンバーから学級委員長を選ぶ投票ツールが完成しました

投票機能はメンバーが増えても減ってもv-forで展開される分しか出力されない&投票機能の各ボタンは個別のVueインスタンスで管理されているのでお互いのボタン操作は干渉しません

従来のjQueryでは「各ボタンを押したらどのメンバーに紐付けたボタンなのか?」や「現在のカウントはいくつか?そっからどう変化(+/-)するか?」という様なロジックで管理する必要があるため、この2〜3倍は記述しないといけないと思います

こうして見れば"再利用可能なパーツ"というイメージもコンポーネント管理できる事の効率化もハッキリわかるのではないでしょうか?

まとめ

今回はjQueryからVue.jsへの移行を考えてる人の「ちょっと体験してみよっかな」の方を対象にしました。

各ディレクティブやオプションの機能だけならば、jQueryやプレーンのJavaScriptでも書き方によってはもっと綺麗にまとめられたり、コントロールできる部分も多いと思いますが、
今回自分でコンポーネント管理までを含めて初めてVue.jsを使う事のメリットを他人にプレゼンできるなという感想です

現段階ではcomputedでできる事ってmethodsでできる事だからいらなくね?」といった初学者モヤモヤももちろん抱えてしまったので、今後もっと掘り下げて行こうと思います

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

トップへ