LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

【輪読会資料】基礎から学ぶVue.js CHAPTER2 データの登録と更新

Last updated at Posted at 2019-02-06

以下の記事は2019/2/7 コワーキングスペース秋葉原Weeybleで行われる輪読会
[秋葉原] 基礎から学ぶVue.js輪読会 ch2 データの登録と更新(初心者歓迎!)の読書メモです。以下、書籍のch2 「データの登録と更新」の書籍メモです。

目次

  • CHAPTER2 データの登録と更新
    • 07 基本のデータバインディング
    • 08 テキストと属性のデータバインディング
    • 09 テンプレートにおける条件分岐
    • 10 リストデータの表示と更新
    • 11 DOMを直接参照する$elと$refs
    • 12 テンプレート制御ディレクティブ

はじめに

本書籍のサポートサイトです。本輪読会ではこちらのサンプルコードを使ってVue.jsの動きを確認していきます。

基礎から学ぶVue.js
基礎から学ぶVue.js-コード&動作デモ-CH2 データの登録と更新

jsfiddle(オンラインエディタ)

CHAPTER2 データの登録と更新

07 基本のデータバインディング

「bind」ー関連付け、割り当て、縛る、紐付ける

*「データバインディング」を行うには、テンプレートで使用するすべてのデータは「リアクティブデータ」として定義する必要がある。
* リアクティブデータとは、Vue.jsによって取得したとき(get)と代入したとき(set)のフック処理が登録された、反応できるデータのこと

リアクティブなデータの定義

  • コンポーネントのdataオプションに文字列やオブジェクトなどのデータを定義
  • インスタンス作成時(どういうとき?)にリアクティブなデータに変換する

var app = new Vue({
    el: '#app',
    data: {                            // dataオプションに文字やオブジェクトデータを定義
        message: 'Vue.js!'  // messageは変化を検地できるようになる
    }                                      // (リアクティブなデータとして定義)
})
  • dataオプションの直下のプロパティは後から追加できないため、内容が決まっていない場合でも初期値や空データとして定義しておく必要がある
初期データの例

data: {
    newTodoText: ' ',
    visitCount: 0,
    hideCompletedTodos: false,
    todos: [],
    error: null
}

08 テキストと属性のデータバインディング

テキストのデータバインディング

「Mustache記法 = {{ message }

Mustacheとはロジックレスのテンプレートエンジンで、その最大のウリはMustacheの記法を覚えると様々な言語で使えるから便利だよ―! ということみたいです。 特定の言語に依存しない記法になっているのは素晴らしいコンセプトで心惹かれます。

Bashでテンプレートエンジン{{Mustache}}を使ってみる


<div id="app">
    <p>Hello {{ message }}</p> 
</div>
実際の描画

<div id="app">
    <p>Hello Vue.js!</p>  //中括弧で記述した部分だけが置き換わる
</div>

◆オブジェクトや配列要素の表示

データバインディング(DataBinding=データ結合・割り当て・紐付け)では、
ルート(?)に定義したプロパティだけではなく、ネスト(nest=入れ子)されているオブジェクトのプロパティや、配列の要素も使用することもできます。

ネストされたデータを定義

// どこがネスト(入れ子)なのか?
new Vue({
    el: '#app',
    data: {
        // オブジェクトデータ
        message: {
            value: 'Hello Vue.js!'
        },
        // 配列データ
        list: ['りんご','ばなな','いちご'] ,
        // 別のデータを使用してlistから取り出す要素を動的に
        num: 1
    }
})

<p>{{ message.value }}</p>
<p>{{ message.value.length }}</p> // どういう意味?
<p>{{ list[2] }}</p>
<p>{{ list[num] }}</p> <!-- プロパティを組み合わせて使用 -->
実際の描画

<p>Hello Vue.js</p>
<p>13</p>
<p>いちご</p>
<p>ばなな</p>

◆式と文の違いに注意しよう!

※CHAPTER4「データの監視と加工」で解説するそうなので、割愛。

2が表示される

{{ 1+1 }}
エラーが発生する

// 次のコードは"式ではなく文"になるため使用できない(?)
{{ var foo = message }}

属性のデータバインディング(v-bind)

「Mustache={{ message }}」はテキストコンテンツ特有の記法のため、属性(value)に使用することはできない。属性(value)を展開するには、v-bindディレクティブを使用する

属性では展開されない

<input type="text" value="{{ message }}">
<!-- Error compiling template -->
属性へのバインドには`v-bind`ディレクティブを使用

// 省略せずに書いた場合
<input type="text" v-bind:value="message">

// 省略して書いた場合(「v-bind:」→「:」)
<input type="text" :value="message">
実際の描画

<input type="text" value="Vue.js!">

v-bindの修飾子(オプション)

修飾子 作用
.prop 属性の代わりにDOMプロパティとしてバインドする
.camel ケバブケースの属性名をキャメルケースに変換する
.sync 双方向バインディングを行う(CHAPTER5で解説)
`.prop`修飾子は、DOMのプロパティと直接バインド(???)

<!-- DOMのtextContentプロパティにバインド -->
<div v-bind:text-content.prop="message">...</div>
<!-- DOMのscrollTopプロパティにバインド -->
<div v-bind:scroll-top.prop="scroll">...</div>

new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue.js!',
        scroll:0
    },
    mounted: function() {
        this.scroll = 100 // 要素のスクロール量を操作(???)
    }
})

データの更新

dataオプション直下のデータは、たとえばmessagecountといった名前を持つプロパティになっているため、これらはすべてリアクティブです。なぜプロパティであることが関係するかについては、この後の「リストデータ」の表示と「更新」のセクション(70ページ)で説明します。

◆クリックでカウンターを増やそう(v-onディレクティブ)

v-onディレクティブを使用して、ボタンをクリックされるとincrementという
名前のメソッドが呼び出されるようにします

ボタンをクリックされると`increment`メソッドを呼び出す

<div id="app">
    <!-- countプロパティを表示する -->
    <p>{{ count }}回クリックしたよ!</p>
    <!-- このボタンをクリックするとincrementメソッドが呼び出される -->
    <button v-on:click="increment">カウントを増やす</button>
</div>
`count`プロパティを「1」増やす処理

new Vue({
    el: '#app',
    data:  {
        count: 0
    },
    methods: {
        // ボタンをクリックしたときのハンドラ
        increment: function() {
            this.count += 1 // 処理は再代入するだけでOK !
        }
    }
})

コラム(thisは今、何を指している?)→パス

◆クラスとスタイルのデータバインディング(???)


<p v-bind:class="{ child: isChild, 'is-active': isActive }">Text</p>
<p v-bind:style="{ color: textColor, backgroundColor: bgColor }">Text</p>

new Vue({
    el: '#app',
    data: {
        isChild: true,
        isActive: true,
        textColor: 'red',
        bgColor: 'lightgray'
    }
})
実際の描画

<p class="child is-active">Text</p>
<p style="color: red; background-color: lightgray">Text</p>

複数の属性のデータバインディング

V-bindディレクティブの引数部分を省略してオブジェクトを渡すと、
一括バインドができる


<img v-bind:src="item-src"
       v-bind:alt="item.alt"
       v-bind:width="item.width"
     v-bind:height="item.height">
   ↓
<img v-bind="item">  // `v-bind`ディレクティブの引数部分を省略

◆SVGのデータバインディング

  • SVGとは?

    Scalable Vector Graphics(スケーラブル・ベクター・グラフィックス)の略で、一種の画像フォーマットになります。JPEGやPNGと言ったようなWebでよく見かけられる画像との違いは、PEGやPNGがビットマップデータなのに対し、SSVGはXMLをベースにした二次元ベクターデータであることです。このベクターデータとは「画像を、点の座標とそれを結ぶ線(ベクター・ベクトル)などの数値データをもとにして演算によって再現する方式」であり、このデータ形式で作られた画像は「拡大・縮小しても画質が損なわれない」といった特徴を持っています<「今こそ取り込むSVG
    とは | 株式会社オプティマイザー)

  • SVGのDOMとは?

SVGでマークアップした円の半径が変化する

<div id="app">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <circle cx="100" cy="75" v-bind:r="radius" fill="lightpink" />
    </svg>
    <input type="range" min="0" max="100" v-model="radius">
</div>

new Vue({
    el: '#app',
    data: {
        radius: 50
    }
})

09 テンプレートにおける条件分岐(v-ifv-showディレクティブ)

  • v-if条件による描画(削除、初期化される)

    条件を満たさない場合、要素はDOMレベルで削除されすべての監視は解除されます。コンポーネントならインスタンスは破壊され、次に描画されるときには状態は初期化されています。

  • v-show条件による表示

    条件を満たさなかった場合、単純にdisplay:none;スタイルを付与します。目に見えなくてても、内側は常にリアクティブで監視されていることに注意しましょう。

v-ifとv-showディレクティブ

<div v-if="ok">v-if条件による描画</div>
<div v-show="ok">v-show条件による表示</div>

new Vue ({
    el: '#app',
    data: {
        ok: false
    }
})

*** タグによるv-ifグループ化***


<template v-if="ok">
    <header>タイトル</header>
    <div>コンテンツ</div>
</template>

*** v-eles-ifおよびv-elseによるグループ化***


<div v-if="type === 'A' ">
    typeはA
</div>
<div v-else-if="type === 'B' ">
    typeはB
</div>
<div v-else>
    すべての条件を満たさなかった
</div>

10 リストデータの表示と更新

要素を繰り返し描画する(v-forディレクティブ)


<li v-for="各要素を代入する変数名 in 繰り返したい配列やオブジェクト">

<div id="app">
    <ul>
        <li v-for="item in list" v-bind:key="item.id">
            ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
        </li>
    </ul>
</div>

new Vue({
    el: '#app',
    data: {
        list: [
        { id: 1, name: 'スライム', hp: 100}.
        { id: 2, name: 'ゴブリン', hp: 200}.
        { id: 3, name: 'ドラゴン', hp: 500}.
実際の描画

<div id="app">
    <ul>
        <li>ID.1 スライム HP.100</li>
        <li>ID.2 ゴブリン HP.200</li>
        <li>ID.3 ドラゴン HP.500</li>
    </ul>
</div>

インデックス(?)とオブジェクトキー(?)の使用


// 変数部分を括弧で囲み、配列インデックスを任意に受け取れます。
<li v-for="(item,index) in list">...</li>

// オブジェクトなら「値」「キー」「インデックス」の順で任意に受け取れます。
<li v-for="(item,key,index) in list">...</li>

繰り返し描画しながら、さまざまな条件を適用する

  • 条件を使ったクラスの操作

// `hp`プロパティが「300」より大きいモンスターのみ
// `.tuyoi`クラスを付与して、さらに「つよい!」の文字を描画

<ul>
    <li v-for="item-list"
         v-bind:key="item.id"
       v-bind:class="{tuyoi: item.hp > 300 }">
    ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    <span v-if="item.hp" > 300">つよい!</span>
    </li>
</ul>
実際の描画

<ul>
    <li>ID.1 スライム HP.100</li>
    <li>ID.2 ゴブリン HP.200</li>
    <li class="tuyoi">ID.3 ドラゴン HP.500<span>つよい!</span></li>
</ul>

描画と表示の条件(要素に直接、v-ifを付与)

<ul>
    <li v-for="item in list" v-bind:key="item.id" v-if="item.hp" < 300">
        ID.{{ item.ID }} {{ item.name }} HP.{{ item.hp }}
    </li>
</ul>
実際の描画
<ul>
    <li>ID.1 スライム HP.100</li>
    <li>ID.2 ゴブリン HP.200</li>
</ul>

リストの更新

  1. インデックス数値を使った配列要素の更新
  2. 後から追加されたプロパティの更新

this.list = []                        // これはプロパティの更新
this.list[0].name = 'NEW'  //  これはプロパティの更新
this.list[0]           = 'NEW'  //  これは配列要素の更新なのでNG!

リストに要素を追加しよう


// リストに新しい要素を追加するには、配列メソッドの`push`や`unshift`を使用
this.list.push(新しい要素)
フォームの入力文字(v-modelディレクティブ)

<!-- このフォームの入力値を新しいモンスターの名前に使う -->
名前<input v-model="name">
<button v-on:click="doAdd">モンスターを追加</button>
<ul>
    <li v-for="item in list" v-bind:key="item.id">
        ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    </li>
</ul>

new Vue({
    el: '#app',
    data: {
        name: 'キマイラ',
        list: [
            { id: 1, name: 'スライム', hp: 100},
            { id: 2, name: 'ゴブリン', hp: 200},
            { id: 3, name: 'ドラゴン', hp: 500}
        ]
    },
    methods: {
        // 追加ボタンをクリックしたときのハンドラ
        doAdd: function() {
            // リスト内で1番大きいIDを取得
            var max = this.list.reduce(function(a,b) {
                return a.id > b.id ? a.id : b.id
            }

リスト要素を削除しよう(配列メソッドsplice)


<ul>
    <li v-for="(item,index) in list"  v-bind:key="item.id">
        ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
        <!-- 削除ボタンをv-for内に作成 -->
        <button v-on:click="doRemove(index)">モンスターを削除</button>
    </li>
</ul>

<button v-on:click="doRemove(index)">モンスターを削除</button>
doRemoveメソッドを定義、spliceメソッドを使用

new Vue({
    // ...
    methods: {
        // 削除ボタンをクリックしたときのハンドラ
        doRemove: function(index) {
            // 受け取ったインデックスの位置から1個要素を削除
            this.list.splice(index,1)
        }
    }
})

リスト要素プロパティを更新しよう

更新対象がプロパティなら、


this.list[0] = { id: 1, name: 'キングスライム', hp: 500}

this.list[0]

this.$set(更新するデータ,インデックス or キー, 新しい値)

// this.set()を使って書き換えると次のように
this.$set(this.list,0,{id: 1, name: 'キングスライム', hp:500})

◆プロパティを追加(this.$setメソッド)


new Vue({
    el: '#app',
    data: {
        list: [
          { id:1, name: 'スライム', hp: 100 },
          { id:2, name: 'ゴブリン', hp: 200 },
          { id:3, name: 'ドラゴン', hp: 500 },
        ]
    },
    created: function() {
        // すべての要素にactiveプロパティを追加したい
        this.list.forEach(function(item) {
            this.$set(item,  'active',  false)
            // 「item.active = false」ではリアクティブにならない
        }, this)
    }
})

◆リスト要素プロパティを更新しよう


<ul>
    <li v-for"(item, index) in list" v-bind:lkey="item.id" v-if="item.hp">
    ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
    <span v-if="item.hp <50">瀕死!</span>
    <!-- ボタンはv-for内に作成 -->
    <button v-on:click="doAttack(index)">攻撃する</button>
    </li>
</ul>

new Vue({
    el: '#app',
    data: { 
        list: [
          { id: 1, name: 'スライム', hp: 100 },
          { id: 2, name: 'ゴブリン', hp: 200 },
          { id: 3, name: 'ドラゴン', hp: 500 }
        ]
    },
    methods: {
        // ボタンのクリックイベントのハンドラ
        doAttack: function(index) {
            this.list[index].hp -= 10 // HPを減らす
        }
    }
})

◆リスト自体を置き換えよう


// listはプロパティなので更新はリアクティブです!
this.list = this.list.filter(function(el) {
    return el.hp >= 100
})

ユニークキーを持たない配列


<select>
    <option v-for="item in list">{{ item }}</option>
</select>

data: {
    list: ['スライム','ゴブリン','ドラゴン']
}

オプションにデータを持たないv-for


//<span>で囲まれた「1~15」の数字を描画
<span v-for="item in 15">{{ item }}</span>

<span v-for="item in [1,5,10,15]">{{ item }}</span>

<span v-for="item in [1, 5, 10, 15]">{{ item }}</span>

文字列に対するv-for


// 文字列にv-forを使用すると、1文字ずつ別々の要素として描画
<span v-for="item in text">{{ item }}</span>

new Vue({
    el: '#app',
    data: {
        text: 'Vue'
    }
})
実際の描画

<span>V</span>
<span>u</span>
<span>e</span>

外部からデータを取得する

list.json

[
    { "id":1, "name": "スライム", "hp": 100 },
    { "id":2, "name": "ゴブリン", "hp": 200 },
    { "id":3, "name": "ドラゴン", "hp": 500 }
]

<div id="app">
    <ul>
        <li v-for="(item, index) in list" v-bind:key="item.id">
            ID.{{ item.id }} {{ item.name }} HP.{{ item.hp }}
        </li>
    </ul>
</div>

new Vue ({
    el: '#app',
    data: {
        // あらかじめ空リストを用意しておく
        list: []
    },
    created: function() {
        axios.get('list.json').then(function(response)) {
            // 取得完了したらlistリストに代入
            this.list = response.data
        }.bind(this)).catch(function(e)  {
            console.error(e)
        })
    }
})

11 DOMを直接参照する$elと$refs(理解度=いまひとつ)

データバインディングを利用することでDOMの更新は効率化されますが、リアルなDOMへのアクセスが必要になるケースもあります。たとえば、要素の画面上の位置や高さはDOMでなければわかりません。
DOMにアクセスするためにはインスタンスプロパティ$elと$refsを使用します。この2つのプロパティは、DOMを参照するため、ライフサイクルのmounted以降でなければ使用できません。

$elの使い方

ルートやコンポーネントのテンプレートを囲んでいるルート要素は、インスタンスプロパティの$elを使って参照できます。


new Vue({
    el: '#app',
    mounted: function() {
        console.log(this.$el) // -> <div id="app"></div>
    }
})

$refsの使い方


<div id="app">
    <p ref="hello">Hello</p>
    <!-- p要素にhelloという名前を付けた -->
</div>

new Vue({
    el: '#app',
    mounted: function() {
        console.log(this.$refs.hello)  // これはp要素のDOM!
    }
})

$elや$refsは一時的な変更!


<div id="app">
    <button v-on:click="handleClick">カウントアップ</button>
    <button v-on:click="show=!show">表示/非表示</button>
    <span ref="count" v-if="show">0</span>

new Vue({
    el: '#app',
    data: {
        show: true
    },
    methods.: {
        handleClick() {
            var count = this.$refs.count
            if (count) {
                count.innerText = parseint(Count.innerText,10)
            }
        }
    }
})

12 テンプレート制御ディレクティブ

ディレクティブ 作用
v-pre テンプレートのコンパイルをスキップする
v-once 一度だけバインディングを行う
v-text Mustacheの代わりにテキストコンテンツを描画
v-html HTMLタグをそのまま描画する
v-cloak インスタンスの準備が終わると取り除かれる

v-pre

v-preディレクティブは、子コンポーネントを含む内側のHTMLのコンパイルをスキッフして性的なコンテンツとして扱います


<a v-bind:href="url" v-pre>
    Hello {{ message }}
</a>

// Mustacheやディレクティブがそのまま表示される
<a v-bind:href="url">Hello {{ message }}</a>

サーバーサイドレンダリング時のXSS対策に用いたり、
操作をする予定のない長いHTML文書を記述している部分に使用すると、
パフォーマンスの向上につながります

v-once

v-onceディレクティブには、初回だけテンプレートを評価しそれ以降は静的なコンテンツとして扱います。


<a v-bind:href="url" v-once>
Hello {{ message }}
</a>

// 1回だけmessageの内容を表示して以降リアクティブを解除
<a href="https//jp.vuejs.org/">Hello.js!</a>

v-text

要素内のテキストコンテンツが単一のMustacheのみで構成される場合、代わりにv-textディレクティブを使って同じようにテキストコンテンツをバインドできます。


new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue.js!'
    }
})
Mustacheの代わりにv-text

<span v-text="message"></span>
実際の描画

<span>Hello Vue.js</span>

v-html

Mustacheを使った描画では、XSS対策のため、必ずHTMLエンティティ化されます。HTMLタグをそのまま表示させたい場合はv-htmlディレクティブを使用します。


new Vue({
    el: '#app',
    data: {
        message: 'Hello <strong>Vue.js!</strong>'
    }
})
Mustacheの代わりにv-html

<span v-html="message"></span>
実際の描画

<span>Hello <strong>Vue.JS!</strong></span>

v-clock


<div id="app" v-cloak>
    {{ message }}
</div>

[v-cloak] { display: none; }

また、次のようなスタイルを定義をすると、画面の読み込み時には#app要素を隠して、インスタンスが作成されるとv-cloak属性が外れフェードインしながら表示します。


@keyframes cloak-in  {
    0% { pacity: 0;}
}
#app {
    animation: cloak-in 1s;
}
#app[v-cloak] {
    opacity: 0;
}

仮想DOMって何?(どなたか説明を・・)

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up