かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Vue.js の勉強メモ(TypeScriptで!)

Vue.js ずっと気になってたんですよね。

jp.vuejs.org

かなり前に React やったんですが、今度は Vue.js に手を出してみたいと思います。

やるならやっぱり…

TypeScriptですよね!以下のコマンドで最新入れておきます。

npm i -g typescript

エディターの設定

Visual Studio Code の Vetur 拡張というのをお勧めされてるので入れておきました。

marketplace.visualstudio.com

ブラウザーの拡張機能

デバッグが便利らしいので、ここら辺も入れておきます。(Chromeに)

chrome.google.com

もしくは、以下のコマンドで専用のツールも入れることが出来るみたい。

npm install -g @vue/devtools

ブラウザー拡張使うのが楽そう。

ガイドをやってみよう

続けて CLI ツールも入れて…と思ったのですが、最初はガイド見てみるといいよ!って書いてあるので見てます。

index.html というファイルを作って以下のようにコードを書きます。

<html lang="ja">
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            {{ message }}
        </div>

        <script type="text/javascript">
            var app = new Vue({
                el: '#app',
                data: {
                    message: 'Hello Vue!'
                }
            });
        </script>
    </body>
</html>

ブラウザーで表示させると…

f:id:okazuki:20190218153735p:plain

いいね!因みに VSCode に Live Server 拡張機能を入れてるので Go Live をぽちっとするだけで html の中身見れて最高。

marketplace.visualstudio.com

この状態で app.message を書き換えるといい感じに更新される。

f:id:okazuki:20190218154459p:plain

そして、属性のバインドも試してみる。これもガイドにある通りに。

<html lang="ja">
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            {{ message }}
        </div>

        <div id="app-2">
            <span v-bind:title="message">
                ホバーしてね
            </span>
        </div>

        <script type="text/javascript">
            var app = new Vue({
                el: '#app',
                data: {
                    message: 'Hello Vue!'
                }
            });

            var app2 = new Vue({
                el: '#app-2',
                data: {
                    message: 'Hello Vue!! ' + new Date().toLocaleString(),
                }
            });
        </script>
    </body>
</html>

その他にも v-ifv-for で分岐やループが出来る。

こんな感じので

<html lang="ja">
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id='app'>
            <ol v-if='seen'>
                <li v-for='message in messages'>
                    {{ message }}
                </li>
            </ol>
        </div>
        <script type="text/javascript">
            var app = new Vue({
                el: '#app',
                data: {
                    seen: true,
                    messages: [
                        '100 回目の',
                        'Hello',
                        'World',
                    ]
                }
            });
        </script>
    </body>
</html>

こうなる

f:id:okazuki:20190218155222p:plain

app.seen = false にするとリストも消える。

f:id:okazuki:20190218155308p:plain

app.seen = true にして app.messages.push('Added item') すると再度表示されて要素も追加された。手軽でいいね。

f:id:okazuki:20190218155409p:plain

v-on でイベントハンドラーの追加と v-model で双方向バインディング。覚えたし。ということでこういうのを書くと…

<html lang="ja">
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id='app'>
            <input type="text" v-model="input" />
            <button v-on:click='addItem'>Add</button>
            <hr />
            <ol>
                <li v-for='x in messages'>
                    {{ x.timestamp }}: {{ x.text }}
                </li>
            </ol>
        </div>
        <script type="text/javascript">
            var app = new Vue({
                el: '#app',
                data: {
                    input: '',
                    messages: [],
                },
                methods: {
                    addItem: function () {
                        this.messages.push({ text: this.input, timestamp: new Date().toLocaleString() });
                        this.input = '';
                    },
                },
            });
        </script>
    </body>
</html>

ボタンを押すとリストに追加される

f:id:okazuki:20190218160355p:plain

コンポーネント

これまではアプリ 1 つで 1 つの Vue オブジェクトだったけどコンポーネントというのを作ることで小さな部品をくみたててアプリに仕立てるということが出来るみたい。

Angular でも React でも同じように小さな部品作ってくみ上げるアプローチだったのできっと Vue.js にもあるだろうと思ってたものがあった。

作り方は

Vue.component('名前', {
  props: [ 'プロパティ名' ],
  data: 初期データを返す関数,
  methods: {
    メソッド名: function() {},
  }
  template: 'レンダリングに使用するタグ',
});

という感じです。

先ほどの入力フォームと結果のリストを、それぞれコンポーネント化するとこんな感じ。

<html lang="ja">

<head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id='app'>
        <input-form v-on:add-click='onAddClick'></input-form>
        <hr />
        <output-list v-bind:messages="items"></output-form>
    </div>
    <script type="text/javascript">
        Vue.component('input-form', {
            data: function () {
                return {
                    input: '',
                };
            },
            methods: {
                addItem: function () {
                    this.$emit('add-click', this.input);
                    this.input = '';
                }
            },
            props: [],
            template: `
                <div>
                    <input type="text" v-model="input" />
                    <button v-on:click="addItem">Add</button>
                </div>
                `,
        });
        Vue.component('output-list', {
            props: ['messages'],
            template: `
                <ol>
                    <li v-for='x in messages'>
                        {{ x.timestamp }}: {{ x.text }}
                    </li>
                </ol>
                `,
        });
        var app = new Vue({
            el: '#app',
            data:  { items: [] },
            methods: {
                onAddClick: function (x) {
                    console.log(x + ' added');
                    this.items.push({ text: x, timestamp: new Date().toLocaleString() });
                },
            },
        });
    </script>
</body>

</html>

f:id:okazuki:20190218170749p:plain

ガイドの中身 + αの内容はこんな感じ。じゃぁ TypeScript してみよう。

TypeScript で作ってみる

ここら辺を参考に。

jp.vuejs.org

上記ドキュメントにも書いてありますが公式の型定義があるのがありがたいですね。

まずは CLI ツールを入れます。

npm i -g @vue/cli

そして、以下のコマンドを打ちます。

vue create hello-world-ts

そうするとウィザードみたいなのが走るので manual を選択して babel じゃなくて TypeScript を選んで適当に進めます。とりあえず、こんな感じで。

$ vue create hello-world-ts


Vue CLI v3.4.0
? Please pick a preset: Manually select features
? Check the features needed for your project: TS, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? No
? Pick a linter / formatter config: TSLint
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

実行が終わると、こんなファイルが生成されてる。

f:id:okazuki:20190218172603p:plain

npm run serve で開発用サーバーを立てたり、npm run build で本番向けビルドもできる。至れり尽くせり。

.vue ファイル

ガイドからいきなりすっ飛んだ気がするけど、Vue.js でのコンポーネントの定義は最終的に .vue というのでやることになるみたい?

まぁそこらへんは今後ドキュメントを読むとして、今のところ .vue ファイルには template タグと script タグと style タグがある。ここにそれぞれテンプレートの HTML とコンポーネントのクラスの定義と、CSS が定義されているように見える。

ということで、新たに components フォルダーに InputForm.vueOutputList.vue を作って内容を以下のようにしてみた。

まずは InputForm.vue

<template>
    <div>
        <input type="text" v-model="input" />
        <button v-on:click="addItem">Add</button>
    </div>
</template>

<script lang='ts'>
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class InputForm extends Vue {
    public input: string = "";

    public addItem() {
        this.addClick(this.input);
        this.input = "";
    }

    @Emit('add-click')
    private addClick(value: string) { }
}
</script>

次に OutputList.vue

<template>
    <ol>
        <li v-for="x in messages">
            {{ x.timestamp }}: {{ x.text }}
        </li>
    </ol>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Message } from '../Message';

@Component
export default class OutputList extends Vue {
    @Prop()
    public messages!: Message[];
}
</script>

そして App.vue

<template>
  <div id="app">
    <InputForm v-on:add-click='onAddClick'></InputForm>
    <hr />
    <OutputList v-bind:messages="items"></OutputList>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import InputForm from './components/InputForm.vue';
import OutputList from './components/OutputList.vue';
import { Message } from './Message';

@Component({
  components: {
    InputForm, OutputList
  },
})
export default class App extends Vue {
  items: Message[] = [];
  onAddClick(x: string) {
    this.items.push(new Message(x));
  }
}
</script>

これで実行すると動いた!

f:id:okazuki:20190218183551p:plain

とりあえず、今日はこんなところで。