Skip to content

Instantly share code, notes, and snippets.

@Shinpeim
Last active December 3, 2018 05:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shinpeim/7dc6850e2fb3cb2d39ac2b5a985d86e7 to your computer and use it in GitHub Desktop.
Save Shinpeim/7dc6850e2fb3cb2d39ac2b5a985d86e7 to your computer and use it in GitHub Desktop.
Vue.js 入門

まずは環境構築するでヤンス

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (vue_hands_on) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) MIT
About to write to /Users/shinpei/nekogata/vue_hands_on/package.json:

{
  "name": "vue_hands_on",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}


Is this ok? (yes) yes

webpackでバベるでヤンス

$ npm install --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-plugin-add-module-exports babel-preset-stage-0
$ npm install --save babel-polyfill

バベル設定するでヤンス

$ vim .babelrc
$ cat .babelrc 
{
    "presets": [
        "es2015",
        "stage-0"
    ],
    "plugins": [
        "add-module-exports"
    ]
}

webpack設定するでヤンス

$ vim webpack.config.js
$ cat webpack.config.js
const path = require('path');

const config = {
  entry: ['./src/js/main.js'],
  output: {
    path: __dirname + "/build/js",
    filename: 'main.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel', // 'babel-loader' is also a legal name to reference
        query: {
          presets: ['es2015']
        }
      },
    ]
  },
  resolve: {
    modulesDirectories: [
      path.resolve("src/js"),
      path.resolve("node_modules"),
      path.resolve("web_modules")
    ]
  },
  plugins: []
};

module.exports = config;

ディレクトリ作っとくでヤンス

$ mkdir -p src/js
$ mkdir -p build/js

npm scriptでビルドできるようにしとくでヤンス

$ vim package.json
$ cat package.json
{
  "name": "vue_hands_on",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --progress",
    "watch": "webpack -d --watch --progress"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "babel-core": "^6.11.4",
    "babel-loader": "^6.2.4",
    "babel-plugin-add-module-exports": "^0.2.1",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-stage-0": "^6.5.0",
    "webpack": "^1.13.1"
  },
  "dependencies": {
    "babel-polyfill": "^6.9.1"
  }
}

hello worldするでヤンス

$ npm install vue --save    
  • build/index.html
<html>
<head>
</head>
<body>
<div id="main">{{message}}</div>
<script src="./js/main.js"></script>
</body>
</html>
  • src/js/main.js
import Vue from "Vue"

new Vue({
    el: '#main',
    data: {
        message: 'Hello, world!'
    }
});
$ npm run build
  • VueクラスのインスタンスがViewModel(Controllerの役割をするやつ)になる。
  • ViewModelはひとつのDOM要素と1:1でbindされる
  • コンストラクタに渡すoptionのdataプロパティが「データバインド」される
  • データバインドされた値はテンプレートからは{{variableName}}で参照できる

世界を書き換えるでヤンス

  • index.html
<html>
<head>
</head>
<body>
<div id="main">
    {{message}}
    <input type="button" value="change the world" v-on:click="changeTheWorld">
</div>
<script src="./js/main.js"></script>
</body>
</html>
  • main.js
import Vue from "Vue"

new Vue({
    el: '#main',
    data: {
        message: 'Hello, world!'
    },
    methods: {
        changeTheWorld: function(){
            console.log(this);
            this.message = 'Hello, changed world!';
        }
    }
});
  • v-onを使うことでVMのメソッドを叩ける
    • methods オプションで定義する
  • 定義したmethod内のthisはVMを指す。
  • dataで生やしたものはVMのインスタンスも持ってる
  • それを書き換えると「データバインド」されてる側も勝手に書きかわる

two way bindするでヤンス

  • index.html
<html>
<head>
</head>
<body>
<div id="main">
    <p>{{message}}</p>
    <input type="input" v-model="message">
    <input type="button" v-on:click="resetMessage" value="reset">
</div>
<script src="./js/main.js"></script>
</body>
</html>
  • main.js
import Vue from "Vue"

const initialMessage = 'Hello, world!';

new Vue({
    el: '#main',
    data: {
        message: initialMessage
    },
    methods: {
        resetMessage: function(){
            this.message = initialMessage
        }
    },
});
  • 双方向binding
    • input系のタグにv-modelを指定すると、ブラウザ上でその内容を書き換えるとdataの内容も書きかわる。
    • dataの内容をJavaScriptで書き換えると、input系タグの内容も書きかわる。

lifecycleを知るでヤンス

https://vuejs.org/images/lifecycle.png

どういうhookがあるかはAPI reference読んでおくと良い

import Vue from "Vue"

const initialMessage = 'Hello, world!';

new Vue({
    el: '#main',
    created: function(){
        this.message = "hello, world";
    },
    data: {
        message: ""
    },
    methods: {
        resetMessage: function(){
            this.message = initialMessage
        }
    },
});

連打マシンを作るでヤンス

  • 連打用ボタンが置いてある

  • 連打された数がわかるカウンタが置いてある

  • ネットワーク越しにみんなで連打できる

  • クリックされるたびにHTTPリクエストを飛ばされたら目も当てられない通信量になる

  • だれかがクリックするたびにサーバーからpushされてきたら目も当てられない通信量になる

  • クライアント側:一定時間クリック数をバッファして定期的にサーバーに送りつける

  • サーバー側:一定時間おきにクライアントに対してpushする

カウンターモデル作るでヤンス

class Counter {
    constructor(){
        this.count = 0;
    }

    increment(){
        this.count = this.count + 1;
    }
}

バッファーしてくれるくんモデル作るでヤンス

class Buffer {
    constructor(){
        this._count = 0;
    }

    increment(){
        this._count++;
    }

    flush(){
        //本来はちゃんとサーバーにpostする
        console.log("flush buffer! count:" + this._count);
        this._count = 0;
    }
}

サーバーからのpush待ち受けくん作るでヤンス

class Listener {
    constructor(counter){
        this._counter = counter;
    }

    start(){
        //本来はちゃんとサーバーに接続してpushを待つ
        let c = 10;
        setInterval(()=>{
            this._counter.count = c;
            c += 10;
        }, 2 * 1000);
    }
}

それらをまとめるApplicationService(アプリケーションのIF)作るでヤンス

class ClickCounterApplicationService {
    /**
     * @param counter {Counter}
     * @param buffer {Buffer}
     * @param listener {Listener}
     */
    constructor(counter, buffer, listener){
        this.counter = counter;
        this._buffer = buffer;
        this._listener = listener;
    }

    start(){
        this._listener.start();
    }
    
    flush(){
        this._buffer.flush();
    }

    click(){
        this.counter.increment();
        this._buffer.increment();
    }
}

VMを作ってApplicationServiceとつなぐでヤンス

import Vue from "Vue";

new Vue({
    el: '#main',
    created: function(){
        const counter = new Counter;
        const buffer = new Buffer();
        const listener = new Listener(counter);
        this.applicationService = new ClickCounterApplicationService(counter, buffer, listener);
        
        this.applicationService.start();
        
        setInterval(() => {
            this.applicationService.flush();
        }, 5 * 1000);
    },
    data: {
        count: 0
    },
    methods: {
        click: function () {
            this.applicationService.click();
        }
    }
});

HTML書き換えるでヤンス

<html>
<head>
</head>
<body>
<div id="main">
    <h1>{{count}}</h1>
    <input type="button" v-on:click="click" value="CLICK ME!">
</div>
<script src="./js/main.js"></script>
</body>
</html>

モデルの変更があったらUIも変更するようにするでヤンス

  • VMがCounterモデルの変更を監視してUIに反映するようにする
// Counterモデルをobservableにする
class Counter {
    set count(c){
        this._count = c;
        this._notify();
    }
    get count(){
        return this._count;
    }

    constructor(){
        this._observers = [];
        this.count = 0;
    }

    increment(){
        this.count = this.count + 1;
    }

    observe(f){
        this._observers.push(f);
    }

    _notify(){
        for (let i = 0; i < this._observers.length; i++) {
            this._observers[i](this.count);
        }
    }
}
import Vue from "Vue";

new Vue({
    el: '#main',
    created: function(){
        const counter = new Counter;
        const buffer = new Buffer();
        const listener = new Listener(counter);
        this.applicationService = new ClickCounterApplicationService(counter, buffer, listener);

        // ここ追加        
        this.applicationService.counter.observe((c) => {
            this.count = c;
        });
        
        this.applicationService.start();
        
        setInterval(() => {
            this.applicationService.flush();
        }, 5 * 1000);
    },
    data: {
        count: 0
    },
    methods: {
        click: function () {
            this.applicationService.click();
        }
    }
});

UIをimproveするでヤンス

new Vue({
    el: '#main',
    created: function(){
        const counter = new Counter;
        const buffer = new Buffer();
        const listener = new Listener(counter);
        this.applicationService = new ClickCounterApplicationService(counter, buffer, listener);
        
        // アニメーションするように変更
        this.applicationService.counter.observe((c) => {
            const i = setInterval(() => {
                if (this.count >= c) {
                    clearInterval(i);
                } else {
                    this.count += 1;
                }
            }, 10);
        });
        
        this.applicationService.start();
        
        setInterval(() => {
            this.applicationService.flush();
        }, 5 * 1000);
    },
    data: {
        count: 0
    },
    methods: {
        click: function () {
            this.applicationService.click();
        }
    }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment