はじめに
既にあるWebアプリケーションのビューをほとんどいじることなく、Vue.js
を導入して、一部の要素を一定間隔で更新することができましたので、ここに紹介します。
はじめた動機とVue.js
の印象
- 既にあるWebアプリケーションのビューの一部の要素を一定間隔で更新したい
-
Angular
、React
などのjavascriptフレームワークをまともに使わずにここまできた -
jquery
で何が悪いの、とまでは言わないが、データをセットするためにDOMを扱いたくない - とはいえ実現したいのは、データバインディングだけで、SPAじゃない
-
Vue.js
(もしくは他のフレームワーク)を導入するからといって、ビューをすべて作り直すようなことはしたくない -
Laravel
はVue.js
をコアに取り込んだらしい -
Vue.js
良さそう from 『私たちはなぜReactではなくVue.jsを選んだのか | プログラミング | POSTD』
Vueインスタンスはアプリケーション?
公式ガイドの「はじめに」をみていると、Vueインスタンスはアプリケーションとして扱うのが流儀みたいにみえます。
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
アプリケーションって具体的に何って話ですが、このコードを見たときの僕の印象は下記のとおりです。
- ビューを構成する単なる一要素ではない?
- 子要素を包括する存在?
- 子要素は
Vue.component
で扱うべき?
VueインスタンスはViewModel?
「はじめに」の次のページに「Vue インスタンス」という章があり、そちらでは、VueインスタンスはViewModelとして扱われています。
var vm = new Vue({
// オプション
})
Vue のデザインは、MVVM パターンと厳密には関連が無いものの、部分的には影響を受けています。
慣例として、 vm (ViewModel の略) を Vue インスタンスの変数名としてよく使います。
シングルページアプリケーションみたいに、全体を管理したい場合は、アプリケーションとして扱って、配下にコンポーネントを配置したほうが効率的で、部品的に使う場合は、ViewModelとして扱うのが良さそうです。
Vueインスタンスを動的に生成、更新するクラスをES6で実装
サーバーサイドで使われる言語と同様、フロントエンドでもES6のclass構文を使って、クラスを実装していきます。
最終的には、下記のクラスを実装し、webpack
+babel
でES5に変換してビュー側で読み込みます。
class SomeViewModels {
constructor(endpoint, selector) {
this.endpoint = endpoint;
this.selector = selector;
}
bind() {
let $elements = $(this.selector);
let self = this;
$.each($elements, function(index, value){
let $element = $(value);
new Vue({
el: '#' + $element.attr('id'),
data: {
id: $element.data('id'),
a: '',
b: '',
c: ''
},
created: function() {
self._getData(this);
}
});
});
}
_getData(vue) {
let self = this;
$.ajax({
url: this.endpoint + '?id=' + vue.id,
dataType: 'json'
}).then(function(data){
self._bindData(vue, data);
}).always(function(){
setTimeout(function(){
self._getData(vue);
}, 60 * 1000);
});
}
_bindData(vue, data) {
if (!data) {
return null;
}
$.each(data, function(index, value){
vue.a = value.a;
vue.b = value.b;
vue.c = value.c;
});
}
}
$(document).ready(function(){
let endpoint = $('#api-endpoint').data('api-endpoint');
let selector = '.some-target';
let vms = new SomeViewModels(endpoint, selector);
vms.bind();
});
尚、テンプレート側は、下記のように、v-text
属性を追加してあげるだけで、データバインドされます。
テンプレート構文( {{ }} )だとエスケープしたりしないといけないので、v-text
属性を使う方が便利です。
<div v-text="a"></div>
<div v-text="b"></div>
<div v-text="c"></div>
その他(環境まわりなど)
webpack
+babel
を使った環境構築するまでの記事はたくさんあるので、ここでは細かく説明しませんが、ポイントになりそうなところは整理しておきます。
Vue.js 本体はCDNのものをそのまま利用
Vue.js自体をwebpackでビルドする方法もありそうなんですが、特に必要性がないので、CDNに置いてあるものをそのまま使います。
httpとhttps両方に対応しているので、スキーマレスで書いても問題ありません。
<script src="//unpkg.com/vue@2.1.10/dist/vue.min.js"></script>
package.json
{
"scripts": {
"build": "webpack"
},
"devDependencies": {
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-runtime": "^6.22.0",
"cross-env": "^3.1.4",
"css-loader": "^0.26.1",
"stylus": "^0.54.5",
"stylus-loader": "^2.4.0",
"webpack": "^2.2.1"
},
"dependencies": {
"es6-promise": "^4.0.5"
}
}
webpack.config.js
webpack
の設定ファイルは下記のとおりです。
const webpack = require('webpack');
module.exports = {
context: __dirname + "/src",
devtool: 'source-map',
entry: {
app: ["./scripts/app.js"]
},
output: {
filename: "scripts/[name].min.js",
libraryTarget: 'var',
path: __dirname + "/dist"
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
sourceMap: true
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015'],
}
}
]
}
};
細かい話ですが、webpack.optimize.UglifyJsPlugin
を使う場合は、引数にsourceMap: true
を指定するとともに、devtool: 'source-map'
を指定しないと、mapファイルが生成されなかったです。
おわりに
結果的に、既にあるWebアプリケーションのテンプレートに、v-text
属性をつけ加えるだけで、対象の要素を一定間隔で更新することができました。
他のフレームワークはともかく、これからは、積極的にVue.js
を使っていこうと思います。
間違っているところ等ありましたら、コメントにてご指摘いただけますと助かります。
ではでは。