# Vue.js

# 基本

# セットアップ

<div id="app" />
var app = new Vue({
  el: '#app',
});

# Interpolation (展開)

<span>{{message}}</span>
// index.js
var app = new Vue({
  data: {
    message: 'hello!',
  },
});

# bind

属性にデータをバインドする。バインドしないと、ただのテキストとして評価される。

<a v-bind:href="someUrl" />
<a :href="someUrl" />
new Vue({
  data: {
    someUrl: 'http://someghing.com/',
  },
});

# if

<span v-if="rock">you rock!</span>
new Vue({
  data: {
    rock: true,
  },
});

# for

<li v-for="todo in todos">
  {{ todo.text }}
</li>
new Vue({
  data: {
    todos: [
      { text: 'Learn JavaScript' },
      { text: 'Learn Vue' },
      { text: 'Build something' },
    ],
  },
});

# on

イベントリスナを設定する。

{{message}}
<button v-on:click="onButtonClick" />
<button @click="onButtonClick" />
new Vue({
  data: {
    message: 'Hello',
  },
  methods: {
    onButtonClick() {
      this.message = 'Hello Again!';
    },
  },
});

# model

two-way binding を設定する。

<input v-model="name" />
new Vue({
  data: {
    name: 'john',
  },
});

# components

<todo-item
  v-for="item in groceryList"
  v-bind:todo="item"
/>
Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>',
});

new Vue({
  data: {
    groceryList: [
      { id: 0, text: 'Vegetables' },
      { id: 1, text: 'Cheese' },
      { id: 2, text: 'Whatever' },
    ],
  },
});

# Vue Instance

# インスタンスの作成

const vm = new Vue(options); // root Vue instance
Vue.component('some-component', options); // other Vue instances

# data

  • data プロパティに渡したオブジェクトは、Vue の Reactivity System に組み込まれる。
  • インスタンスからの変更、元データの変更は、双方向に反映される。
  • data の変更は自動的に View に反映される。
  • data に後からプロパティを追加することはできない。
var data = { a: 1 };

var vm = new Vue({
  data: data,
});

vm.a === data.a; // => true

vm.a = 2;
data.a; // => 2

data.a = 3;
vm.a; // => 3

# 規定のプロパティ・メソッド

インスタンスには、名前が$で始まる、規定のプロパティとメソッドがある。

参考 (opens new window)

vm.$data === data; // => true
vm.$el === document.getElementById('example'); // => true

vm.$watch('a', function(newValue, oldValue) {
  // This callback will be called when `vm.a` changes
});

# Lifecycle Hooks

  • いくつかのライフサイクルメソッドがある
  • ライフサイクルメソッドの中のthisは、常に Vue インスタンスを指す(アロー関数は使えないので注意する)

lifecycle

# Template Syntax

  • テンプレートは Valid な HTML である。
  • テンプレートを使わずに、JSX とrenderファンクションを使うこともできる。

# Interpolation(Vue のデータをテンプレートに展開する)

# Text

  • Mustache 記法を使う
<span>Message: {{ msg }}</span>
<span v-once>This will never change: {{ msg }}</span> // 最初の1回のみ更新

<!-- 下記は機能しない。代わりにv-modelを使うこと。 -->
<textarea>{{text}}</textarea>

# Raw HTML

  • v-html ディレクティブを使う
<span v-html="rawHtml" />

# Attributes

  • 属性の中では Mustache 記法は使えない。代わりに v-bind を使う。
  • null, undefined, false の場合、属性はレンダリングされない
<div v-bind:id="dynamicId"></div>
<div v-bind:active="isActive"></div> // falsyならactiveはレンダリングされない

# Javascript

テンプレートには 1 文までの Javascript を記載できる。

{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

# Directives

  • v-**がディレクティブである
  • ディレクティブには 1 文までの Javascript を記載できる

# modifier

v-onv-modelには Modifier という便利なものが用意されている(後述)

# Computed Properties and Watchers

# computed

  • 複雑な計算にはcomputedプロパティを使う。
  • comuptedは Getter として機能する。
  • computedが依存するdataがアップデートされたときは自動で再計算され、View に反映される
<p>{{ reversedMessage }}</p>
var vm = new Vue({
  data: {
    message: 'Hello',
  },
  computed: {
    reversedMessage: function() {
      return this.message.split('');
    },
  },
});

# computed と method の違い

computedと同じことはmethodsでもできる。違いは下記の通り。

  • computedは、依存するdataが変更されない限り再計算をしない(キャッシュが使われる)
  • methodsは、常に再計算をする

# computed と watch の違い

殆どの場合watchでデータを変更するのは効率が悪い (opens new window)computedを使え。

watchが最適となるのは、データの変更に応じて、非同期 or 高価な処理を行う場合に限る(参考 (opens new window)

# setter

computedは、標準では getter としてのみ機能する。 setter を使いたいときは下記のようにする。

computed: {
  fullName: {
    get: function () {},
    set: function (newValue) {}
  }
}

# Class and Style Bindings

# Classes

  • v-bind:classを使うことで、クラスを動的に設定できる
  • オブジェクトはインラインでなくても OK(外出ししてもよい)
  • computedで計算したオブジェクトを使うと、便利で強力である
  • Array Syntax で複数の要素を指定することもできる
<div class="some-default-class"
     v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

<!-- errorClass='some-string' -->
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

# Component に Class を指定したとき

Component に Class を指定したときは、そのコンポーネントのルート要素にそのクラスが設定される。

<my-component class="baz boo"></my-component>

# Styles

  • Class の場合とほぼ同じ
  • Auto Prefix される
<div v-bind:style="styleObject"></div>
<div v-bind:style="[baseStyles, overridingStyles]"></div>

# Conditional Rendering

# v-if

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

# template

2 つ以上の要素にまとめて v-if を設定したいときは template を使う。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

# DOM の再利用

  • v-if は、DOM を再利用する。再利用させたくない場合はkey属性を指定する。
  • 下記の場合、label は再利用されるが、input は再利用されない。
<template v-if="loginType === 'username'">
  <label>Username</label>
  <input key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input key="email-input">
</template>

# v-show

v-if との違いは下記の通り

v-if

  • 条件が True になったとき、初めて内包する要素が生成される。
  • 条件が False になったときは要素が削除される。
  • トグルのコストが高い
  • あまりトグルしないものに向いている

v-show

  • template, v-else は使えない
  • 条件に関係なく常に生成される。css の display を変更しているだけ。
  • 初期表示のコストが高い
  • 頻繁にトグルするものに向いている

# v-if と v-for

  • v-ifv-forを同時に使った場合、v-forが優先される。
  • つまり、v-forの各子要素に対してv-ifがアタッチされる

# List Rendering

# v-for

  • Array や Object にループ処理を行った上でレンダリングするためのもの。
  • inofに置き換えてもよい。
<!-- Array -->
<li v-for="item in array"></li>
<li v-for="(item, index) in array"></li>

<!-- Object -->
<li v-for="item in object"></li>
<div v-for="(value, key) in object">
<div v-for="(value, key, index) in object">

# key

リスト要素には、必ず key 要素をつけること。 (デフォルトの"in-place patch"という方法を意図的に使いたい場合を除く)

# Array の変更検知

data の Array に対して行った、push(),pop(),shift(),unshift(),splice(),sort(),reverse()などの変更は、自動的に View に反映される。

filter()concat()等の元データを変更しないメソッドの場合は、元データを書き換えるのを忘れないこと。

また、下記の操作は Vue が検知できないので注意。

// インデックスを使用した値の変更
vm.items[indexOfItem] = newValue;
// Arrayのlengthの編集
vm.items.length = newLength;

そんなときはVue.setspliceを使うこと

Vue.set(vm.items, indexOfItem, newValue);
vm.items.splice(indexOfItem, 1, newValue);

# Object の変更検知

ルートレベルのプロパティの追加は Vue が検知できない。

var vm = new Vue({
  data: {
    a: 1,
    userProfile: {},
  },
});
vm.b = 2; // `vm.b` is NOT reactive

ルートレベルでなければ、set を使うことで追加は可能

Vue.set(vm.userProfile, 'age', 27);

// setではなくObject.Assignなどを使いたいときは、
// 元のオブジェクトは捨てて、フレッシュなオブジェクトをセットすること
vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green',
});

# 配列にフィルタ・ソートをかけるには

フィルタ・ソートをかけたいときは、computedormethodを使うと便利。

data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

# 指定回数だけ v-for を実行

<span v-for="n in 10">{{ n }} </span>
<!-- 1,2,3,,,,,10 -->

# 複数の要素を繰り返す

v-if と同じく、template を使う。

<template v-for="item in items">
  <li>{{ item.msg }}</li>
  <li class="divider" role="presentation"></li>
</template>

# v-for をコンポーネントで使う

通常の要素と同じように、コンポーネントにも v-for が使える。 ただし、コンポーネント内からは外部の値にアクセス出来ないので、必要なものは明示的に props として渡す必要がある。

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

# Event Handling

# イベントを Listen する

<!-- `counter` is the name of a variable -->
<button v-on:click="counter += 1">Add 1</button>

<!-- `greet` is the name of a method. JSXと異なり、()をつけてもInvokeされない -->
<button v-on:click="greet">Greet</button>
<button v-on:click="greet('hello')">Greet</button>

<!-- イベントを渡したいときは$eventを使う -->
<button v-on:click="greet('hello', $event)"></button>

# Event Modifier

  • DOM 専用
    • .stop propagation を止める(バブリングフェーズでの外側のイベントの発生を止める)
    • .prevent preventDefault()
    • .capture capture モードにする(キャプチャフェーズでイベントを発生させる)
    • .self ターゲットが自分自身であったときのみイベントを発生させる
    • .passive passive イベントとして設定(preventDefault しないことを宣言。参考資料 (opens new window)
  • DOM/Component で使用可
    • .once 一度だけ実行

# 注意点

  • 適用順に注意
    • v-on:click.prevent.self
      • バブリングを含めた全てのフェーズにおいて prevent する
      • ターゲットが自分自身のフェーズにおいてのみ、click イベントが発生する
    • v-on:click.self.prevent
      • ターゲットが自分自身のフェーズにおいてのみ click イベントが発生かつ prevent する
  • 当然だが、preventpassiveは同時に使用できない

# Key Modifier

<input v-on:keyup.13="submit">
<!-- same as above -->
<input v-on:keyup.enter="submit">

エイリアスの一覧

  • .enter
  • .tab
  • .delete (captures both “Delete” and “Backspace” keys)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

KeyboardEvent.key (opens new window)をケバブケースにしたものも使用可能。

<input @keyup.page-down="onPageDown">

独自のエイリアス設定も可能

Vue.config.keyCodes.f1 = 112;

# System Modifier Key

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

# exact Modifier

exactを指定すると、指定したキーのみが押されているときのみイベントが発生する。

<!-- this will fire even if Alt or Shift is also pressed -->
<button @click.ctrl="onClick">A</button>

<!-- this will only fire when Ctrl and no other keys are pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>

# Mouse Button Midifier

  • .left
  • .right
  • .middle

# Form Input Bindings

# 基本

  • v-modelを使う
  • IME 環境では確定されるまで data は変更されない。確定前の入力を捕捉したい場合はinputイベントを使って自前で実装すること。

# Text

<input v-model="message">

# Multiline text

<textarea v-model="message"></textarea>

# Checkbox

<!-- checkedはbooleanになる -->
<input type="checkbox" id="checkbox" v-model="checked">

<!-- もし、toggleに特定の文字列を入れたい場合 -->
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no"
>

<!-- checkedNamesは、valueの値からなる配列になる -->
<div>
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <span>Checked names: {{ checkedNames }}</span>
</div>

# Radio

pickedには value の値が入る。

<input type="radio" id="one" value="One" v-model="picked">
<input type="radio" id="two" value="Two" v-model="picked">

# Select(単一選択)

  • selectedにはoptionで囲んだ値が入る
  • iOS で問題が起こるので、1 行目はdisabledとし、空の value を設定したほうがよい
<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

# Select(複数選択)

<!-- selectedはArrayになる -->
<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

# その他

valueの値は、string 以外にも、v-bind した値を使用することもできる。この場合、string だけでなく、オブジェクトや数値を渡すことができる。

# Modifier

  • .lazy input イベントではなく change イベント(フォーカスを失った時)の際に data を更新する。
  • .number 文字列ではなく数値として扱う
  • .trim 余分な空白等を削る
<input v-model.lazy="msg" >
<input v-model.number="age" type="number">
<input v-model.trim="msg">

# Components Basics

# 基本

コンポーネントは、root Vue とほぼ同じプロパティを持つ。相違点は次のとおり。

  • elがない
  • dataはファンクションにする必要がある

# 注意点

  • コンポーネントはシングルルート要素でなければならない

  • template リテラルは IE では使えないので注意。使うなら babel でトランスパイルする。

    template = `
    multiline
    `;
    

# global と local

global に宣言すると、root Vue instance の中のどこからでも使える。

Vue.component('my-component-name');

# props

コンポーネントはコンポーネントの外の値にアクセスできない。 アクセスするには、props を使って明示的に値を渡す必要がある。

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>',
});

# Emitting Event

コンポーネントから親にイベントを渡すには、this.$emitを使う。

<!-- component -->
<button v-on:click="$emit('enlarge-text')"></button>
<button v-on:click="$emit('enlarge-text', 2)"></button>

<!-- parent -->
<!-- 引数があり、かつメソッドの()を省略した場合、自動的に第一引数に渡される -->
<blog-post :enlarge-text="alert()"></blog-post>
<blog-post :enlarge-text="alert($event)"></blog-post>
<blog-post :enlarge-text="onEnlargeText"></blog-post>

# コンポーネントで v-model を使うには

v-bind は、内部的には次の 2 つの機能から成り立っている。

  • valueprop へのバインディング
  • inputイベントによるデータの更新
<input v-model="searchText">
<!-- これは下記と等価 -->
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

コンポーネントの場合は、上記を念頭に置き、下記のようにする。

<!-- parent -->
<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

<!-- component -->
<input
  v-bind:value="value"
  v-on:input="$emit('input', $event.target.value)"
>

<!-- ここまで来たら、parentは下記の通り書き換えてもOK -->
<custom-input v-model="searchText" />

なお、標準では、v-model は、valueprops とinputevent を使うので、 checkbox などを使うときは、下記のような工夫が必要。

Vue.component('base-checkbox', {
  model: {
    // v-modelに、valueの代わりに`checked`を使え、と伝える
    prop: 'checked',
    // v-modelに、inputイベントの代わりに`change`イベントを見ろ、と伝える
    event: 'change',
  },
  props: {
    checked: Boolean,
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `,
});

# Slot

React での children と同じ。

<!-- parent -->
<alert-box>
  Something bad happened.
</alert-box>

<!-- component -->
<div>
  <slot></slot> <!-- ここに`Something bad happened.`が入る -->
</div>

# Dynamic Components

  • コンポーネントを動的に切り替えたい場合は、component要素とis属性を使う。
  • isの値には次のいずれかを指定する
    • 登録済みのコンポーネントの名称を入れた変数
    • コンポーネントを作成するときのoptionに相当するオブジェクト
<component :is="currentTabComponent"></component>

# DOM テンプレートパース時の警告

  • ul,ol,tableのようないくつかの HTML 要素には、それらの要素の中でどの要素が現れるかに制限がある。そんなときはis属性を使うこと。
  • なお、下記の中であればこの制約は該当しない
    • templateプロパティの中
    • .vueファイルの中
    • <script type="text/x-template">の中
<!-- 下記は認められない -->
<table>
  <blog-post-row></blog-post-row>
</table>

<!-- 下記のようにすべし -->
<table>
  <tr is="blog-post-row"></tr>
</table>

# Component Registration

# 名前の付け方

  • 全て小文字、必ずハイフンを含める(W3C)
  • PascalCase で宣言すると、ケバブ、パスカルのどちらでもアクセスできる。ただし、DOM の中ではケバブのみが valid である点に留意する

# Global Registration

Vue.component('component-a', {});
Vue.component('component-b', {});

component-b から component-a を利用できる。

# Local Registration

var ComponentA = {};
var ComponentB = {};

new Vue({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB,
  },
});

component-b から component-a は利用できない。利用するには下記のようにする。

var ComponentA = {};

var ComponentB = {
  components: {
    'component-a': ComponentA,
  },
};

// もしくは、webpack等を使っている場合、ComponentB.vueにおいて
import ComponentA from './ComponentA.vue';

export default {
  components: {
    ComponentA,
  },
};

# Base Component

# Props

# camel vs kebab

props の名前を camelCase にした場合は、props を渡す時に kebab-case にする必要がある。 ただし、string templates の中ではこの制約は該当しない。

Vue.component('blog-post', {
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>',
});
<!-- DOMテンプレート -->
<blog-post post-title="hello!"></blog-post>
<!-- string templateの中では下記でもOK -->
<blog-post postTitle="hello!"></blog-post>

# Prop Types & Validation

Prop Types を使いたいときは、配列ではなく、オブジェクトで指定する。

Vue.component('my-component', {
  props: {
    // Basic type check (`null` matches any type)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true,
    },
    // Number with a default value
    propD: {
      type: Number,
      default: 100,
    },
    // Object with a default value
    propE: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function
      default: function() {
        return { message: 'hello' };
      },
    },
    // Custom validator function
    propF: {
      validator: function(value) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].indexOf(value) !== -1;
      },
    },
  },
});

Type として使えるもの

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
  • コンストラクタ関数(instanceof でチェックされる)

# props に様々な種類のデータを渡す

v-bind を使うと、様々な Javascript の値を渡すことができる。

<!-- 数値として -->
<blog-post :likes="42"></blog-post>

<!-- オブジェクトの値を渡す -->
<blog-post :likes="post.likes"></blog-post>

<!-- Booleanとして -->
<blog-post :is-published="false"></blog-post>

<!-- 配列として -->
<blog-post :comment-ids="[234, 266, 273]"></blog-post>

<!-- オブジェクトとして -->
<blog-post :author="{ name: 'Veronica', company: 'Veridian Dynamics' }"></blog-post>

# オブジェクトのプロパティを分解して渡す

v-bind=を使うことで全てのプロパティを分解して渡せる。

post: {
  id: 1,
  title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
<!-- 上記は下記と等価 -->
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

# One-way data flow

いかなる場合でも、props の値は変更するな。 変えたいなら、あくまでdataの初期値として利用するにとどめるか、computed を使うなどしろ。

# コンポーネントに対して Props に記載してない属性を渡すとどうなる?

  • コンポーネントのルート要素にアタッチされる。
  • その際、class と style についてはマージされる。それ以外はまるごと置換えられるので注意。

この機能を無効にするには:

Vue.component('my-component', {
  inheritAttrs: false,
});

無効にしても、$attrsを使うことで属性の取得は行える。これは、Base Component を作る時に特に便利。

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        :value="value"
        :input="$emit('input', $event.target.value)"
      >
    </label>
  `,
});
<base-input
  label="something" // `label` propsになる
  v-model="username" // `value` propsになる。また、inputイベントを受けて値を更新する。
  class="username-input" // input要素にアタッチされる
  placeholder="Enter your username" // input要素にアタッチされる
/>

# Custom Events

# イベント名

イベント名には常に kebab-case を使え。例外はない。

# ネイティブイベントを補足する

  • .native modifier を使うと、コンポーネントのルート要素のイベントを捕捉できる。
  • ルート要素以外のイベントを捕捉するには工夫 (opens new window)が必要。
<base-input v-on:focus.native="onFocus"></base-input>

# .sync modifier

  • 親とコンポーネントの間で擬似的な two-way binding を行うためのもの。
  • キモはupdate:の記法と.syncがセットになっていること。
// コンポーネント側
this.$emit('update:title', newTitle);
<!-- 親 -->
<text-document
  :title="doc.title"
  @update:title="doc.title = $event"
></text-document>

<!-- 上記は下記と等価 -->
<text-document :title.sync="doc.title"></text-document>

# Slot

# Scope

親テンプレート内の全てのものは親のスコープでコンパイルされ、子テンプレート内の全てものは子のスコープでコンパイルされる

# Named Slot

<!-- component -->
<div class="container">
  <header>
    <slot name="header">Default Content</slot>
  </header>
  <main>
    <slot>Default Content</slot>
  </main>
  <footer>
    <slot name="footer">Default Content</slot>
  </footer>
</div>

<!-- parent -->
<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

# Scoped Slots

slot-scope属性を指定することで、slot(子)の属性に、外側(親)からアクセスできる。

<!-- component -->
<slot :a="1" normalAttr="2"></slot>

<!-- parent -->
<todo-list>
  <p slot-scope="slotProps">{{ slotProps }}</p>
  <!-- { "a": 1, "normalAttr": "2" } -->
</todo-list>

# Dynamic & Async Components

# Dynamic Component(keep-alive)

  • isプロパティを使ってコンポーネントを切り替えると、切り替える前のコンポーネントは破棄される。 破棄したくない場合は、keep-alive要素で囲むこと。
  • この機能は、コンポーネントが name を持っている場合にのみ機能するので注意すること
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

# Async Components

コンポーネントを非同期に作成する方法

Vue.component('async-example', option);

// 方法1(ファンクションを使う)
const option = function(resolve, reject) {
  setTimeout(function() {
    resolve({
      template: '<div>I am async!</div>',
    });
  }, 1000);
};

// 方法2(Webpack の code-splitting の機能 を使用)
const option = function(resolve) {
  require(['./my-async-component'], resolve);
};

// 方法3(Promiseを使う方法。importはPromiseを返す)
const option = () => import('./my-async-component');

# Handling Edge Cases

# 親へのアクセス

下記のような便利な変数もあるが、デバッグ目的でのみ使うこと。

  • $root root instance の値にアクセス
  • $parent 親の値にアクセス

# 子へのアクセス

ref を使用することで、要素への参照を取得できる

<input ref="usernameInput"></input>
this.$refs.input.focus();

コンポーネントの中の要素への参照を取得したい場合はこちら (opens new window)を参照

# Dependency Injection

React の Context に近い。親コンポーネントのデータを、子・孫コンポーネントで使いたい時に使う。多用厳禁(Vuex を使え)。

// 親コンポーネント
provide: function () {
  return {
    getMap: this.getMap
  }
}

// 子 or 孫コンポーネント
inject: ['getMap']

# Programatic Event Listener

  • $emit イベントを発生させる
  • $on イベントを Listen する
  • $off イベントの Listen をやめる
  • $once イベントを一度だけ Listen する($offがいらない?)

これらをうまく使うとコードをきれいに書ける場合がある。

例:

mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })

    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

# 循環参照の解決

循環参照 = 互いに依存するコンポーネント

Vue.componentを使ってグローバル登録した場合は Vue が自動的に問題を解消するが、webpack を使っている場合は下記のエラーが出る。

Failed to mount component: template or render function not defined.

これを解決するには、親となるコンポーネントで次のようにする(詳細 (opens new window))。

// $optionsを使う方法
beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

// webpackのimportを使う方法
components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

# テンプレートの作り方(番外編)

どちらも使うな

  • Inline Template
  • X-Template

# Enter/Leave & List Transitions

# 単一の要素・コンポーネントのトランジション

transitionコンポーネントでラップすると、enteringleavingのトランジションを設定できる。ラップできる要素 or コンポーネントの条件は下記の通り。

  • v-ifが使われている
  • v-showが使われている
  • Dynamic Component である
  • Component root nodes である
<transition>
  <p v-if="show">hello</p>
</transition>
.v-enter,
.v-leave-to {
  transform: translateX(10px);
  opacity: 0;
}

.v-enter-active,
.v-leave-active {
  transition: all 0.5s;
}

transitionで囲まれた要素が挿入・削除されたときに、次のことが起こる。

  • CSS クラスが適切なタイミングで挿入される
  • JavaScript hooks が適切なタイミングで実行される
  • アニメーション・トランジションも、JS Hooks もない場合は、DOM 操作が即時に実行される。

# transition classes

6 種類の class が付与される。

  • v-enter, v-leave 最初の 1 フレームだけに付与される
  • v-enter-to, v-leave-to 2 フレーム目から最後まで付与される
  • v-enter-active,v-leave-active 最初から最後のフレームまで付与される。duration, delay, easing curve の設定に使う。

image

v-の部分は、transition要素の name 属性によって変わる。 <transition>の場合はv-, <transition name="my">ならmy-になる。

# CSS Transitions を使った例

前述の通り

# CSS Animations を使った例

.v-enter-active {
  animation: bounce-in 0.5s;
}
.v-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

# カスタムクラス

Animate.css (opens new window) などのライブラリを使うときは、transition要素に下記の属性をつけることで、カスタムクラスを設定して対応する。

  • enter-class
  • enter-active-class
  • enter-to-class
  • leave-class
  • leave-active-class
  • leave-to-class
  • move-class
<transition
  enter-active-class="animated tada"
  leave-active-class="animated bounceOutRight"
>

# animation と transition の同時利用

同時利用するときは Vue がアニメーションの終わりを判定できないので、個別に設定 (opens new window)が必要。

# Explicit Transition Durations

Vue はアニメーションやトランジションの終了をtransitionend or animationendイベントで判定する。いくつかのケースでこれが不適切な場合があるので、そういうときは明示的に時間を指定する。

<transition :duration="1000" />
<transition :duration="{ enter: 500, leave: 800 }" />

# Javascript Hooks

Velocity.js (opens new window)など、JS ベースのアニメーションライブラリを使うときは、Javascript Hooks を使う。詳細はこちら (opens new window)

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"

  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
/>
const option = {
  methods: {
    beforeEnter: function(el) {},
    enter: function(el, done) {},
    afterEnter: function(el) {},
    enterCancelled: function(el) {},

    beforeLeave: function(el) {},
    leave: function(el, done) {},
    afterLeave: function(el) {},
    leaveCancelled: function(el) {},
  },
};

# 初期表示時のトランジション

通常、初回表示の際はトランジションが適用されないが、appear属性をつけることでトランジションを適用することができる。その他、細かい設定 (opens new window)も可能。

<transition appear></transition>

# 複数の要素のトランジション

transitionの中で複数の要素を扱うときは、それぞれにkey属性を設定すること。そうしないと、DOM が再利用されてトランジションが適用されない。

<transition>
  <button v-if="docState === 'saved'" key="saved">
    Edit
  </button>
  <button v-if="docState === 'edited'" key="edited">
    Save
  </button>
  <button v-if="docState === 'editing'" key="editing">
    Cancel
  </button>
</transition>

# Transition Mode

2 つ以上の要素にトランジションをかける時、どの順番でトランジションするかを設定できる。指定しなかった場合は、同時に実行される。

  • out-in 古い要素のトランジションが完了後、新しい方のトランジションを開始
  • in-out 上記の逆。あまり使わない。
<transition name="fade" mode="out-in">

# 複数のコンポーネントのトランジション

複数のコンポーネントのトランジションには、ダイナミックコンポーネントを使用する。key属性は不要。

<transition>
  <component v-bind:is="view"></component>
</transition>
new Vue({
  data: {
    view: 'v-a',
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>',
    },
    'v-b': {
      template: '<div>Component B</div>',
    },
  },
});
.v-enter-active,
.v-leave-active {
  transition: opacity 0.3s ease;
}
.v-enter,
.v-leave-to {
  opacity: 0;
}

# リスト要素のトランジション

v-forなどにトランジションを設定するには<transition-group>を使用する。

  • transitionと異なり、transition-groupは実際に要素をレンダリングする。デフォルトはspanなので変更したいときはtag属性を指定する。
  • Transition Mode は使えない
  • リストの各要素にkey属性が必須
  • name属性でクラス名(v-の部分)が変わる挙動は、transitionと同じ。

# List Entering & Leaving Transitions

<transition-group tag="div">
  <span v-for="item in items" v-bind:key="item" class="list-item">{{ item }}</span>
</transition-group>
.v-enter-active,
.v-leave-active {
  transition: all 1s;
}
.v-enter, .v-leave-to /* .list-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}

# リスト要素の移動のトランジション

v-moveクラスを使う。他のクラスと同じく、transition-groupname属性によりルックアップするクラス名が変わる。また、move-class属性を指定することでクラス名をカスタマイズすることも可能。

.v-move {
  transition: transform 1s;
}
.v-leave-active {
  position: absolute;
}

注意点

  • inlineでは使えない。inline-box or flexを使うこと。
  • 要素の削除時 = v-leave-active時は position を absolute にしないと、v-moveが効かない。詳細はサンプルを参照。

# Staggering List Transitions

時間差でリストを畳んだり表示したりする。JavaScript で制御する。詳細はドキュメント (opens new window)参照。

# Transition の再利用

slotを使って再利用できる Transition を作ると便利。詳細はドキュメント (opens new window)参照。

# Dynamic Transitions

  • トランジションのname属性を動的に変更することで、トランジションをダイナミックにすることができる。
  • もしくは、Javascript Hooks を使う。

# State Transitions

数値のトランジションには Tween.js, 色のトランジションには Color.js などが使える。必要になったときにドキュメント (opens new window)を参照する。

# Mixins

mixin = コンポーネント作成時につかうoptionの雛形

var myMixin = {
  created: function() {
    this.hello();
  },
  methods: {
    hello: function() {
      console.log('hello from mixin!');
    },
  },
};

var MyComponent = new Vue.extend({
  mixins: [mixin],
  // ...
}

# マージ戦略

  • data sharrow merge (1 階層目だけ)される。重複時は mixin 側が破棄される
  • Lifecycle method Array になる。全て保持される(mixin の方が最初に実行される)
  • methods, components, directives 重複時は mixin 側が破棄される

マージ戦略のカスタマイズ方法はこちら (opens new window)

# Global Mixin

使うな

Vue.mixin({
  /*...*/
});

# Custom Directives

自分だけのv-***を作ることができる。詳細はこちら (opens new window)

// v-focus を作りたい場合

// グローバル
Vue.directive('focus', {
  inserted: function (el) {
    el.focus()
  }
})

// ローカル(コンポーネントごと)
directives: {
  focus: {
    inserted: function (el) {
      el.focus()
    }
  }
}

# Render Functions & JSX

TODO

# Plugins

プラグインのタイプ

  • グローバルなメソッドとプロパティを追加する
  • グローバルなアセットを追加する(directives/filters/transitions etc)
  • コンポーネントの option を mixin で追加する(e.g. vue-router)
  • Vue instanse メソッドを追加する(Vue.prototype を使う)
  • 上記のいずれかと組み合わせて、API を追加する(e.g. vue-router)

プラグインの使い方

Vue.use(MyPlugin, options);

new Vue({});

# Filter

Filter は、Mustache 記法の中と、v-bind の中で使える。

<!-- in mustaches -->
{{ message | capitalize }}

<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>

宣言方法

// ローカル
filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

// グローバル
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({})

なお、Filter に引数を渡した場合は、それらは function の第2引数以降に渡される。

{{ message | filterA('string', someValue) }}

<!-- function (message, arg1, arg2)) -->

# Production Deployment

参照 (opens new window)

# Single File Components

webpack+vue-loaderにより、シングルファイルコンポーネントを利用することで、次のことが可能になる。

  • シンタックスハイライト
  • テンプレートエンジンの利用
  • プリプロセッサの利用
  • scoped CSS の利用 など

single file component

# Unit Testing

# Typescript

# コンポーネントの Type

コンポーネントにタイプをアタッチするには、Vue.component or Vue.extendでコンポーネントを作ること。

# Class-Style Vue Components

vue-class-component (opens new window)を使えば Vue コンポーネントをクラスで記載できる。

# Routing

vue-router (opens new window)を使え

# State Management

vuex (opens new window)を使え

vue-devtools を使えばタイムトラベルデバッグもできる

# Server Side Rendering

Nuxt (opens new window)を使え

もしくはこちらのガイド (opens new window)を参照して自前でやれ