Vue.jsとその周辺技術を学ぶ勉強会

## はじめに
社内発表用として作成中の資料です。
（4/14発表しました。）

---

## Vue.jsとは
### [1.x]
・ MVVMという設計思想を用いている
・ 同じMVVMのKnockout.jsより、分かりやすくてシンプルなAPIを提供しているらしい
・ Vue.jsのいくつかのAPIはAngularJSから影響を受けているらしい（ただし設計思想は全く異なる）
・ AngularJSの経験者はVue.jsのディレクティブを見ると、どうゆうものかすぐ分かると思います。

### [2.x]
・ 2016年10月に正式版リリースされた新しいライブラリ
・ コンポーネント指向を取り入れている
・ 仮想DOMを採用
・ React.jsとAngularの良い所を採用して作られたようなライブラリ

---

## コンポーネント指向
Vue.jsはReactなどで採用されているコンポーネント指向を取り入れている。
MVVMはModel, View, ViewModelに分けて、モジュールを開発していたのに対して、
コンポーネント指向ではDOM(HTML, JS, CSSを一纏めにした)単位でモジュールを開発する。

例えば、以下のようにVueファイルを実装する。

```html:sample.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  name: 'hello',
  data () {
    return {
      msg: 'message!!'
    }
  }
}
</script>
<style>
h1 {
  font-weight: normal;
}
</style>
```

コンポーネント間のデータの受け渡しは、親コンポーネントから子コンポーネントへはprops経由で渡し、
子から親へはイベントを発火させることで連携する。

この辺を次ページで詳細に書きます。

---

## 親子コンポーネント間のデータバインディングについて
・ 単方向のデータバインディング
・ 親コンポーネントの状態を更新した時に、子コンポーネントへ伝えて反映させる。
・ 子から親への状態更新は不可（Warningが出る）。

以下のように書くことで、親の変更を子へ反映させることができる。

```html:parent.html
<child v-bind:msg="parentMsg"></child>
```

```js:parent.js
export default {
  data: function () {
    return {
      parentMsg: "parent message!!!"
    }
  }
}
```

↓ v-bindは省略可。

```html
<child :msg="parentMsg"></child>
```

子コンポーネントでは、propsを介して値を受け取る。
型制限や必須チェックなども記載できて、これに反したデータを受け取るとWarningを出す。

```js
export default {
  props: {
    msg: {
      type: String,
      required: true,
      default: "default message",
      validator: function (value) {
        return value.length > 1
      }
    }
  }
}
```

---

## 双方向データバインディング
データを更新したらUIにも反映 + UIを変更したらデータにも反映。

```html
<input type="text" v-model="parentMsg">
```

```js
new Vue({
  data: {
    parentMsg: 'message!!'
  }
})
```

↑data.parentMsgの「message!!」をテキストボックスに出力し、
その後UI上のテキストボックスが入力されたらdata.parentMsgが更新される。

また、先程の親子関係があるコンポーネント間では使ってはいけません。
以下のように親から受け取ったmsgをv-modelで書いてしまうとWarningがでます。

```html:child.html
<input type="text" v-model="msg">
```

```js:child.js
export default {
  props: {
    msg: {
      type: String,
    }
  }
}
```

それでも変更したい時は、子コンポーネント内で別のプロパティとして定義する。

```html:child.html
<input type="text" v-model="childMsg">
```

```js:child.js
export default {
  props: {
    msg: {
      type: String,
    }
  },
  data: function () {
    return {
      childMsg: this.msg
    }
  }
}
```

単一のコンポーネントでは、双方向のデータバインディングは許されるが、
複数のコンポーネント間（親子関係の）では、単方向のデータバインディングで実装する。

---

## 子コンポーネントで起きたイベントを親へ伝える
子で出力している値（テキストボックスの値とかチェックボックスのチェック状態とか）を親で管理している場合、
子で起きたイベントを親へ伝えるような実装にする必要がある。

以下のようにchildMsgとonChangeMsgを子へpropsで渡す。
onChangeMsgはコールバック関数として渡せるので、イベント発生時に親へ通知できる。

```html:parent.html
<child :childMsg="childMsg" :onChangeMsg="onChangeMsg"></child>
```

```js:parent.js
export default {
  data: function () {
    return {
      childMsg: "child message!!!"
    }
  },
  methods: {
    onChangeMsg: function (msg) {
      this.childMsg = msg
    }
  }
}
```

子ではprops経由で受け取ったonChangeMsgをv-onを使って書くことで、
イベント発生時に親へ伝えることができる。

```html:child.html
<input type="text" v-on="change:onChangeMsg">{{ childMsg }}</input>
```

```js:child.js
export default {
  props: {
    childMsg: {
      type: String,
    },
    onChangeMsg: {
      type: Function,
    }
  }
}
```

↓ v-onは省略可。

```html
<input type="text" @change="onChangeMsg">{{ childMsg }}</input>
```

---

## 親子関係まとめ
先程のことを意識して実装すると、再利用性が高いコンポーネントを作りやすくなる。
また大規模アプリケーションを検討する際に、Fluxのようなアーキテクチャの導入・移行がかなり楽になる。

---

## Fluxの導入について
Viewから状態管理に関わる処理を分離する為に導入する。
中・大規模なアプリケーションになってくると、親コンポーネントが肥大化してくるので、
状態管理に関わる処理の部分は、Flux系のフレームワークに任せて、
親コンポーネントは状態変更させるアクションと変更後の状態を受け取り、
子コンポーネントに渡す役割をすることで、Viewの実装をシンプルにしてくれる。

---

## Fluxについて
<img width="634" alt=" 2017-03-17 14.15.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/95b405e9-d332-3dd0-d5c3-6965f411ccf6.png">
※画像出典：[UNIDIRECTIONAL USER INTERFACE ARCHITECTURES](http://staltz.com/unidirectional-user-interface-architectures.html)

Actions：Viewの入力内容を基にデータを作成
Dispatcher：Actionの命令に合わせて、Storeにデータを送る
Store：データを更新して保管
View：データを表示

### Flux導入のメリット
データを更新した際の流れが単方向であり、MVVMフレームワークのような双方向な流れが無いため、分かりやすい

### デメリット
Viewでデータを更新する際はActionから実装するため、冗長構成になりやすい

---

## Redux
<img width="634" alt=" 2017-03-17 14.58.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/287e6849-b0ef-177b-b989-ef6f259d4a33.png">
※画像出典：[UNIDIRECTIONAL USER INTERFACE ARCHITECTURES](http://staltz.com/unidirectional-user-interface-architectures.html)

Fluxに以下の制約を加えたもの
・ Single source of truth (真実の単一の源)
⇒システム全体のStateを１つのStoreで管理する。

・ State in read-only (読み込み専用の状態)
⇒StateをViewなどで更新してはいけない。Stateを更新する場合はActionを通じてのみにする。

・ Changes are made with pure functions (変更は純粋な関数で行われます)
⇒ReducerでActionと現在のStateを受け取り、新しいStateを作って返す。その際現在のStateは変更せず、新しいStateを作り出して返す。

---

## Vuex
<img width="538" alt=" 2017-03-17 14.02.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/a3ae98b2-64c9-72fe-9a53-1323ba7e8f72.png">
※画像出典：[Vuex Introduction](https://vuex.vuejs.org/ja/intro.html)

Vue.jsの場合は、こちらを使うことが多いかもしれない。

Reduxと考え方が似ており、Reducerの代わりにMutationがその役割を担う。
ReduxはReducerでActionTypeを指定するので、Actionに対して単体もしくは複数のReducerが紐づき、処理する。
VuexはActionでMutationTypeを指定するので、Actionから単体もしくは複数のMutationを呼出し、処理する。

図には無いが、StateからViewの間にGettersというのがVuex内にあり、StateをViewで出力する用に加工したい時などに使える。
ViewでStateを直接受け取って加工すれば、特にGettersは使わなくても良いが、加工したStateを複数コンポーネントで使う場合などは、Gettersで処理しておけば、冗長的に書かずに済む。

---

## CSSModulesの導入について
ある程度システム規模が大きくなると、CSSのグローバルスコープでは影響範囲の把握が辛いものがあるので、
CSSModulesを導入して、コンポーネントごとにローカルスコープ化した。

以下の設定をすることで、実現できる。

```html
<template>
  <div :class="$style.sample" />
</template>
<style module>
.sample {
  margin-top: 10px;
}
</style>
```

`<style module>`と書くことで、内部的にWebpackの`css-loader`で処理されて変換される。
また、命名規則を変えたいなどの追加オプションは、Webpackの`vue-loader`のオプションで指定できる。

```js:webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: [{
          loader: 'vue-loader',
          options: {
            cssModules: {
              localIdentName: '[name]__[local]___[hash:base64:5]',
              camelCase: true
            }
          }
        }],
        exclude: /node_modules/
      }
    ]
  }
}
```

CSSModulesでは、命名を意識せずにローカルスコープ化できるので、その点おすすめ
BEMなどのCSS設計を導入・浸透させるのに苦戦していたら、CSSModulesを試してみるのも良いかと思う。

---

## ESLintの導入
コードの統一やレビュアーのストレス軽減の為、ESLint（コードの静的検証）を導入している。

```js:webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$|\.js$/,
        use: 'eslint-loader',
        exclude: /node_modules/
      },
    ]
  }
}
```

Vue.jsのLintは`eslint-plugin-vue`,`eslint-config-vue`を使用した。

```js:.eslintrc
"root": true,
"parser": "babel-eslint",
"parserOptions": {
  "sourceType": "module"
},
"extends": "vue",
"plugins": [
  "html"
]
```

ESLintではなく、以下のようなコードフォーマッタ的なのもある。
https://github.com/prettier/prettier

コードフォーマッタとLintを両方使うことも可能だが、たぶん競合するので、
`prettier-eslint`等を使って、回避する必要がある。
この辺はそのうち検証しようと思ってます。

---

## UnitTestについて
コンポーネントとVuexのAction, Getter, MutationでUnitTestを書いている。
Karma, Mocha, Chaiで構成してて、`vue-cli`で使われてるライブラリと一緒です。
（いくつか他のライブラリも検討しましたが、結局これにしました。）

```js:karma.conf.js
const webpack = require('webpack')
const webpackConfig = require('../../gulp/webpack.test.config')

module.exports = (config) => {
  config.set({
    frameworks: ['mocha', 'sinon-chai'],
    files: [
      '../../node_modules/babel-polyfill/dist/polyfill.js',
      '../../src/**/*.vue',
      '../../src/**/*.js',
      {
        pattern: 'specs/**/*.js',
        watched: false
      }
    ],
    preprocessors: {
      '../../src/**/*.vue': ['webpack'],
      '../../src/**/*.js': ['webpack'],
      'specs/**/*.js': ['webpack', 'sourcemap']
    },
    proxies: {
      '/api': 'http://example.aaa.jp:8888/api'
    },
    reporters: ['spec', 'coverage'],
    browsers: ['PhantomJS'],
    webpack: webpackConfig,
    webpackMiddleware: {
      quiet: true
    },
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'text-summary' },
        { type: 'html' }
      ]
    }
  })
}
```

カバレッジの出力も行っていて、`babel-plugin-istanbul`を使うとBabel変換時にカバレッジを計測してくれる。
上記のKarmaの設定に加えて、以下をBabelに設定しておく。

```js:.babelrc
{
  "env": {
    "test": {
      "plugins": ["istanbul"]
    }
  }
}
```

---

## 番外編１ methodsとcomputedはどちらを使うか
以下はgetValue1もgetValue2も計算結果の2を画面に表示する。

```js
export default {
  methods: {
    getValue1: function () {
      return 1 + 1
    }
  },
  computed: {
    getValue2: function () {
      return 1 + 1
    }
  }
}
```

```html
<div>
  {{ getValue1() }}
  {{ getValue2 }}
</div>
```

これらの使い分けとして、
computedはキャッシュが使えるのに対して、methodsはキャッシュせずに毎回計算する。
computedは依存関係にあるプロパティが更新された時のみ、再評価してくれる。
methodsはキャッシュを使いたくない時に使用すれば良い。

また、computedはgetter/setterが書ける。

```js
export default {
  data () {
    return {
      sampleName: 'sample'
    }
  },
  computed: {
    getName: {
      get: function () {
        return this.sampleName
      },
      set: function (str) {
        this.sampleName = str
      }
    }
  }
}
```

---

## 番外編２ テンプレートの書き方について
テンプレート（html）の実装方法は以下の２通りがある。
Vue1系からやっている人は、①の書き方に慣れてる方が多いかもしれません。

```js:①
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
```

```html:②
<template>
  <div>
    A custom component!
  </div>
</template>
```

これらの違いは、コンパイルされるタイミングで
①はブラウザで実行前にコンパイルされる（JustInTimeコンパイル）
②はWebpack等で事前にコンパイルされる

①はコンパイラが必要になるので、完全ビルド
②はコンパイラが不要なので、ランタイム限定ビルドを利用できる。

Vueはデフォルトでランタイム限定ビルドとなるので、①のようなtemplateオプションは読み込まれない。
かつ、ランタイム限定ビルドの方が軽いので、特に理由が無ければ②の書き方にしましょう。

やむを得ず①の書き方にしたい場合は、バンドラーでエイリアスを設定する。
↓Webpackでの例

```js:webpack.config.js
module.exports = {
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}
```

---

## 番外編３ IEについて
IE以外のブラウザはJSの部分は問題無く動くと思いますが、IEは別途対応が必要。

### IE8以下
サポート対象外

### IE9以上
babel-polyfillが無いと動かなかった。
Webpackでbabel-polyfillを読み込んであげると動くようになる。

```js:webpack.config.js
module.exports = {
  entry: {
    bundle: [
      `${__dirname}/../node_modules/babel-polyfill/dist/polyfill.js`,
      `${__dirname}/../src/index.js`
    ]
  }
}
```