Help us understand the problem. What is going on with this article?

vue.js + typescript = vue.ts ことはじめ

vue.js は typescript を公式にサポートしています。
つい先日 vue-cli のバージョン 3.0 にてついに、初めから typescript が導入された vue.js のプロジェクトテンプレートが追加されました。

いよいよ vue.js ならぬ vue.ts に追い風が吹いてきたのを感じます。

そこでその追い風を更に強くするべく vue.js + typescript でコンポーネントを作るための基礎知識というかチュートリアルっぽいものを書いてみました。

この記事の概要

  • vue.js を typescript で書く
  • vue-class-component を利用する
  • vue-property-decolator を利用する
  • ドキュメントにない細かい機能や tips

でお送りします。
目標はコンポーネントを自由に作れるようになることです。

プロジェクトテンプレート

何をするにしてもプロジェクトがないと始まりません。
この章では vue-cli を利用して vue.js を typescript で書けるようにセットアップしていきます。

vue-cli

vue-cli は vue.js のプロジェクトを準備するとき等に利用するツールです。
vue-cli でプロジェクトを作りましょう。

vue-cli を利用するには node.js をインストールして npm を利用して vue-cli をインストールします。

npm install -g @vue/cli

インストールが終わったら早速 vue-cli のプロジェクトテンプレートを利用してプロジェクトを作りましょう。
プロジェクトは vue create コマンドで作成します。
コマンドは vue create <project-name> で指定します。

vue create vue-ts-practice

起動するとプリセット選択に移ります。
Typescript を利用するために Manually select features を選択します。
1.jpg

Typescript と Router を選択して実行します。Linter はお好みで(これは外しています)。
2.jpg

以降設定について聞かれるので vue-class-component については Yes にし、あとはお好みで設定したら、こんな感じでプロジェクト作成が始まるので完了するまで待ちます。
3.jpg
完了すると以下のような画面になると思います。
image.png
青文字で記述されているように

cd <プロジェクト名>
npm run serve

と実行すると構築されたプロジェクトが起動されます。
5.jpg
表示されているアドレス(ttp://localhost:8080)にブラウザで接続してみると Vue.js のウェルカムページに遷移します。
6.jpg
このプロジェクトを利用して Typescript で Vue.js を書いていきます。

小さなコンポーネント

vue.js はコンポーネントというものを作成し、それらを組み合わせて大きなシステムを作ることができます。
コンポーネントは全ての基本です。
まずは小さなコンポーネントを作って、網羅的にその機能を知っていきましょう。

初めてのボタン

ボタンはコンポーネントを学ぶのに最適な玩具です。
手始めにボタンのコンポーネントを作り、コンポーネントの基礎を学びましょう。

手始めに作るのはMyButtonというコンポーネントです。
コンポーネントを作るために src/components/MyButton.vue というファイルを用意し次のように書きます。

src/components/MyButton.vue
<template>
    <button>MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue{
    }
</script>

@マークで始まる単語はアノテーションと呼ばれるものです。
フレームワーク等のヒントとすべく、クラスやフィールドなどに目印として添付します。
@Componentはこれから何十回と書くアノテーションになるでしょう。

また typescript らしく記述するためにextendsキーワードを使ってVueクラスを継承しています。
これも何度も記述することになると思います。

以上で最小限のコンポーネントを作ることができました。
早速このコンポーネントを表示しましょう。

現在の Web ページの画面は Home.vue の内容が表示されています。
ここにMyButtonを表示すべく次のように書き換えましょう。

src/views/Home.vue
<template>
    <div class="home">
        <MyButton></MyButton>
    </div>
</template>

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

    @Component({
        components: {
            MyButton,
    },
    })
    export default class Home extends Vue {}
</script>

なお<MyButton></MyButton>という部分は<my-button></my-button>というケバブケースにしても構いません。
それがコンポーネントであることを強調するのであればアッパーキャメルケース、html のふりをしたいならケバブケースでしょうか。

.vue ファイルを書き換えると自動的に変更が適用されて、ブラウザに表示されているページに変更がすぐさま適用されると思います。
image.png
おめでとうございます!
MyButtonはあなたの初めての typescript 製コンポーネントです。

プロパティ

コンポーネントには親子関係があります。
もしも上位のコンポーネントのデータを下位のコンポーネントで利用したいと思ったとき、プロパティを利用します。

つまりコンポーネントにデータを渡す時に利用するのがプロパティです。

例えばMyButtonに「挨拶」をさせてみましょう。
その「挨拶の内容」はボタン毎に決めたいです。
「挨拶の内容」はプロパティです。

挨拶の内容をプロパティとして受け取れるようにMyButtonを変更します。

src/components/MyButton.vue
<template>
    <button>MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        @Prop()
        public greet?: string;
    }
</script>

これでプロパティを準備できました。

greet? のクエスチョンマークはその値が設定されないことも有りうると示唆することになります。
必ず設定されることを保証する時はgreet!とエクスクラメーションマークで記述します。

さぁMyButtonを利用しているHomeからデータを渡しましょう。

src/views/Home.vue
<template>
    <div class="home">
        <MyButton greet="Hello"></MyButton>
    </div>
</template>

greet プロパティに "Hello" という文字列を渡しました。
これでMyButton.greetには "Hello" という文字列が代入されています。

しかし今のままではボタンを押しても何も起きないので「挨拶」できません。
挨拶ができるように click イベントをハンドリングしましょう。

イベントハンドラ

DOM 要素はいくつもイベントを持っています。
button はクリックしたときのイベント、input は入力したときのイベント。
発生するイベントは DOM によって様々でしょう。

それらのイベントを受け取ってコンポーネントはユーザからの入力を認識して処理を行います。
イベントハンドラはそのイベントをハンドリングするためのメソッドです。

早速 click イベントをハンドリングするイベントハンドラをMyButtonに定義して挨拶をさせましょう。

src/components/MyButton.vue
<template>
    <button @click="onClick">MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        @Prop()
        public greet?: string;

        public onClick(){
            alert(this.greet);
        }
    }
</script>

イベントハンドラはメソッドとして定義して登録したいイベントを @ マーク付きの名前で指定して登録します。
この状態でボタンを押すと上手く挨拶をさせることができるでしょう(上手くいかない場合はリロードしてみてください)。
image.png

プロパティとバインディング

コンポーネントが定義したプロパティは上位コンポーネントで設定された値を監視しています。
設定された値が変更された場合、その変更は自動的に下位コンポーネントのプロパティにも適用されます。

この機能をバインディングと言います。

バインディングを試してみましょう。
バインディングは:greet='field'といった形でプロパティ名にコロンをつけることでバインディングができます。

src/views/Home.vue
<template>
    <div class="home">
        <MyButton :greet="greetText"></MyButton>
    </div>
</template>

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

    @Component({
        components: {
            MyButton,
        },
    })
    export default class Home extends Vue {
        public greetText: string = "Hello";
    }
</script>

このスクリプトは "Hello" と挨拶してくれるでしょう。

イベント

二度目の挨拶は日本語にしてみましょう。

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

    @Component
    export default class MyButton extends Vue {
        @Prop()
        public greet?: string;

        public onClick(){
            alert(this.greet);
            this.greet = "こんにちは";
        }
    }
</script>

二度目以降ボタンを押すと次のような表示になると確認できます。
image.png
上手くいきましたね。
しかし Console を見てみましょう(Chrome なら F12 を押します)。
image.png
なんということでしょう。
得体のしれないエラーが表示されていますね。
何がいけなかったのでしょうか。

このエラーは実はMyButtonコンポーネントが自身にバインディングされたプロパティを直接変更されているために発生しています。
つまり、コンポーネントはプロパティを変更してはいけないのです。

ではどうやって値を変更すればよいのでしょうか。

そのときに利用されるのがイベントです。
イベントはコンポーネントで発生した出来事を、自身をコンポジションする上位コンポーネントに伝えるための機能です。

MyButton にボタンが押されたことを上位コンポーネントに伝えるイベントを追加してみましょう。

src/components/MyButton.vue
<template>
    <button @click="onClick">MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        @Prop()
        public greet?: string;

        @Emit()
        public click(){
        }

        public onClick(){
            alert(this.greet);
            this.click();
        }
    }
</script>

@Emit アノテーションが設定されたメソッドはイベントとして見なされます。
つまり、このメソッドを呼ぶことでイベントが発火されるようになります。

発火されたメソッドは上位コンポーネントで @clicked='callbackMethod'という形でイベントハンドラの登録を行います。
通常のボタンなどのイベントハンドラと同じで、登録されたメソッドはイベントが発生したときに(下位コンポーネントでメソッドが呼ばれたときに)実行されます。

src/views/Home.vue
<template>
    <div class="home">
        <MyButton :greet="greetText" @click="onMyButtonClicked"></MyButton>
    </div>
</template>

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

    @Component({
        components: {
            MyButton,
        },
    })
    export default class Home extends Vue {
        public greetText: string = "Hello";

        public onMyButtonClicked(){
            this.greetText = "こんにちは";
        }
    }
</script>

ボタンを押してもエラーは発生しなくなりました。

データの表示

フィールドやプロパティの値を文字として表示したいことはよくあるでしょう。
その場合にそのフィールドを {{field}} といった形で表示することができます。

いちいちボタンを押さないとデータが見えないのはわかりづらいので、現在の挨拶文をHome.vueで表示してみましょう。

src/views/Home.vue
<template>
    <div class="home">
        <p>{{greetText}}</p>
        <MyButton :greet="greetText" @click="onMyButtonClick"></MyButton>
    </div>
</template>

greetText の内容は常に表示されるようになりました。
image.png

プロパティとイベントと親子関係

上位コンポーネントから下位コンポーネントへデータを伝える場合はプロパティによるデータバインディングで伝えます。
下位コンポーネントから上位コンポーネントへデータを伝える場合はイベントを利用します。

少し面倒に見えますね。
一見すると下位コンポーネントがバインディングされた値を変えてしまえば、その変更は上位コンポーネントに伝わるのでそれでいいじゃないかと思うところです。
わざわざイベントなどというものを介在してデータを渡すより余程シンプルに思えます。

何故それがエラーとされているのでしょうか。
理由を考えてみましょう。

もしも下位コンポーネントが上位コンポーネントの値を書き換えてもよいというルールにすると、上位コンポーネントは下位コンポーネントによるデータ変更を常に考慮しなくてはいけなくなります。

データは不意に null にされてしまうかもしれません。
データが配列だった場合、いつのまにかデータが増えているかもしれませんし、減っているかもしれません。
上位コンポーネントは常に下位コンポーネントの所作に怯えることになります。

本来データを変更するのはそのデータを保持しているクラスであるべきです。

関連するコンポーネントの随所でデータを変更されてしまうと、コードリーディングやデバッグの際にデータ変更について確認したいときは、関連するコンポーネント全てを確認する必要があります。

こういった問題を避けるルールとして、下位コンポーネントから上位コンポーネントのデータを変更する手段としてはイベントを利用するようにしています。

モデルバインド

とはいえ、全てをイベントでやり取りしようとするとそれはそれで冗長な処理になりがちです。

例えば input 要素などは、その値の変更とバインドされたプロパティは同期しても問題ない場合が多いです。
元々が単純なものなので、いちいちプロパティで値をバインディングして、イベントハンドラを書いて、そのイベントハンドラでフィールドを変更して……といった手続きを愚直に記述するのは冗長すぎると感じるのは当然です。

そもそもの問題は「不意に」データが変更されてしまう点です。
逆に言えば「意識して」データが変更されてしまう分には問題がありません。

それを達成するにはコンポーネント自身が「ここにバインドされた値はコンポーネントが変更することがあるぞ」と主張すれば問題ないのではないでしょうか。

これを vue.js ではモデルバインドという機能で実現します。

例えば押されたらモデルバインドされたプロパティを初期値に戻すResetButton.vueを作ってみましょう。

src/components/ResetButton.vue
<template>
    <button @click="onClick">Reset</button>
</template>

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

    @Component
    export default class ResetButton extends Vue {
        @Prop()
        public initialValue!: string;

        /** モデルバインドのために記述必須 */
        @Prop()
        public value!: string;

        /** モデルバインドのために記述必須 */
        @Emit()
        public input(value: string) {
        }

        public onClick(){
            this.input(this.initialValue);
        }
    }
</script>

モデルバインドをする場合はこのようにvalueというプロパティとinputというイベントを定義しなくてはなりません。
このコンポーネントは「モデルバインドをしたときにバインドした値は書き換えられる」というルールを主張していることになるので、あとは使う側がそれを意識すればよいだけのことです。
逆にこういった記述がない場合は、変更されることを想定する必要はありません。

このコンポーネントへのモデルバインドは次のように記述します。

src/views/Home.vue
<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>
            <MyButton :greet="greetText" @click="onMyButtonClicked"></MyButton>
        </p>
        <p>
            <ResetButton initialValue="Hello" v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import MyButton from "@/components/MyButton.vue";
    import ResetButton from "@/components/ResetButton.vue";

    @Component({
        components: {
            MyButton,
            ResetButton,
        },
    })
    export default class Home extends Vue {
        public greetText: string = "Hello";

        public onMyButtonClicked(){
            this.greetText = "こんにちは";
        }
    }
</script>

これでResetButtonを押すgreetTextフィールドがinitialValueとして設定された "Hello" に戻ります。
image.png
       ↓
    Reset をクリック
       ↓
image.png
使いどころさえ間違えなければ便利な機能です。

必須パラメータ

ところで、今のResetButtonは初期値が設定されていなくても動作します。

<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>
            <MyButton :greet="greetText" @click="onMyButtonClick"></MyButton>
        </p>
        <p>
            <ResetButton v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

しかしリセットという特性上リセットしたときに元に戻すパラメータはあってしかるべきです。
そういったときにプロパティにそれが必須項目であるという制約を記述する方法があります。

@Prop({required: true})
public initialValue!: string; 

このような記述でそれが必須項目と主張させることができます。
コンポーネントの利用者はこの設定を目安に必須項目と任意項目を見分けてプロパティを設定することができます。

……とはいえ実はこの設定はコンパイルエラーではなくワーニングになるだけなので余り頼りにならなかったりします。
image.png
個人的な意見ですが、コンポーネントを触るときはなるべくライトに触ってみたかったりします。
なんとなく触ってみてよさそうだったらもう少し使い込んでみる、といったような感じで。
そういった人からするとなるべく必須項目は少なく動作してくれると嬉しかったりはします。

どうすればライトなリセットボタンになるでしょうか。

ライフサイクルフック

vue のコンポーネントにはライフサイクルがあります。
そのコンポーネントが生まれてから不要になるまで、つまり生まれてから死ぬまで=ライフサイクルです。

例えばHome.vueは / にアクセスすると生成され、他の url に遷移すると破棄されます。
コンポーネントを作っていると生まれた瞬間や破壊される瞬間に処理を挟みたい場合があります。
その処理を挟む方法として処理を Hook するライフサイクルフックという機能があります。

今回でいえばResetButtonはおあつらえ向きでしょう。

今現在ResetButtonは初期値としてinitialValueに値を設定しています。
しかしgreetTextという値をバインディングしているのですから、それの最初の値を初期値としてくれればそれで事足りる気がします。

それを実現するためにコンポーネントが「生成された瞬間」をライフサイクルフックしてみましょう。

src/components/ResetButton.vue
<template>
    <button @click="onClick">Reset</button>
</template>

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

    @Component
    export default class ResetButton extends Vue {
        private initialValue!: string;

        @Prop()
        public value!: string;

        /** ライフサイクルフック */
        public created(){
            this.initialValue = this.value;
        }

        @Emit()
        public input(value: string) {
        }

        public onClick(){
            this.input(this.initialValue);
        }
    }
</script>

created メソッド(生成された瞬間に呼ばれる処理です)を定義するだけでライフサイクルフックが可能です。
これでinitialValueをプロパティとして公開する必要がなくなりました。

ライフサイクルフックについて詳しく知りたい場合は vue.js 公式をご参考ください。
https://jp.vuejs.org/v2/guide/instance.html#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF

ルーティングのフック

ルーティングが変更されたときのフックもあります。
もしもコンポーネントでルーティングをフックしたい場合は

Component.registerHooks([
  'beforeRouteEnter'
])

と記述をしてbeforeRouteEnterメソッドを定義する必要があります。

例えば描画する前にデータを取ってきて、そのデータ如何によってルーティングを変更したい時などに利用されます。
詳しくはこちらをご覧ください。
https://nrslib.com/vuejs_beforerouteenter/

下位コンポーネントからのデータ送出

下位コンポーネントはイベントを経由して上位コンポーネントにデータを渡すことができます。
例えば挨拶をした回数を伝えてあげましょう。

src/components/MyButton.vue
<template>
    <button @click="onClick">MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        private count: number = 0;

        @Prop()
        public greet?: string;

        // 引数を追加しました 
        @Emit()
        public click(count: number){
        }

        public onClick(){
            alert(this.greet);
            this.count++;
            this.click(this.count);
        }
    }
</script>

上位コンポーネントは次のようになります。

src/views/Home.vue
<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>挨拶した回数 : {{count}}</p>
        <p>
            <MyButton :greet="greetText" @click="onMyButtonClick"></MyButton>
        </p>
        <p>
            <ResetButton v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import MyButton from "@/components/MyButton.vue";
    import ResetButton from "@/components/ResetButton.vue";

    @Component({
        components: {
            ResetButton,
            MyButton,
        },
    })
    export default class Home extends Vue {
        private count: number = 0;
        public greetText: string = "Hello";

        public onMyButtonClick(count: number){ // ← 引数で受け取る
            this.count = count;
            this.greetText = "こんにちは";
        }
    }
</script>

image.png

算出プロパティ

ある値に紐づく値というものは存在します。

例えば5回以上挨拶をした人を常連さんと認識して「いつもありがとうございます」という特別なメッセージを表示するシステムを追加しましょう。

src/views/Home.vue
<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>挨拶した回数 : {{count}}</p>
        <p v-if="isRegulars">いつもありがとうございます</p>
        <p>
            <MyButton :greet="greetText" @click="onMyButtonClick"></MyButton>
        </p>
        <p>
            <ResetButton v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import MyButton from "@/components/MyButton.vue";
    import ResetButton from "@/components/ResetButton.vue";

    @Component({
        components: {
            ResetButton,
            MyButton,
        },
    })
    export default class Home extends Vue {
        private count: number = 0;
        private isRegulars: boolean = false;
        public greetText: string = "Hello";

        public onMyButtonClick(count: number){
            this.count = count;
            if(this.count >= 5){
                this.isRegulars = true;
            }
            this.greetText = "こんにちは";
        }
    }
</script>

onMyButtonClick メソッドが少し騒がしいですがこれは求めた結果を得ることができます。
(v-if という書き方でその DOM 要素を表示非表示させることができます。詳しくはディレクティブで)

しかし、こういった場合にもっとスマートな記述方法があります。
それが算出プロパティです。

算出プロパティは getter で記述をします。

@Component({
    components: {
        ResetButton,
        MyButton,
    },
})
export default class Home extends Vue {
    private count: number = 0;
    public greetText: string = "Hello";

    public get isRegulars(): boolean{
        return this.count >= 5;
    }

    public onMyButtonClick(count: number){
        this.count = count;
        this.greetText = "こんにちは";
    }
}

isRegularscountというフィールドと密接な関係があり、また余計なフィールドが減り、スマートな実装になりました。
これは vue.js でいうところのcomputedにあたる処理です。

Watch

さらに追加の仕様です。
常連になったときだけ特別なメッセージをアラートしてあげましょう

public get isRegulars(): boolean{
    if(this.count === 5){
        alert("常連になりました");
    }
    return this.count >= 5;
}

ちょっと待って!
これはハックしすぎです。

こういうときは@Watchを使うべきです。

import { Component, Watch, Vue } from 'vue-property-decorator';

export default class Home extends Vue {
    private count: number = 0;
    public greetText: string = "Hello";

    public get isRegulars(): boolean{
        return this.count >= 5;
    }

    @Watch('count')
    public countChanged(){
        if(this.count === 5){
            alert("常連になりました");
        }
    }

    public onMyButtonClick(count: number){
        this.count = count;
        this.greetText = "こんにちは";
    }
}

@Watch は文字列で指定されたフィールドやプロパティを監視し、その値が変更されるたびに設定されたメソッドを呼び出します。
何かの値に紐づけて、振る舞いを実行したい場合に利用してください。

スロット

ところでMyButtonのラベルをわかりやすくするため「挨拶する」に変更したいと思います。

MyButton.vue
<template>
    <button @click="onClick">挨拶する</button>
</template>

他の画面では「こんにちは」にしたい場合もあります。
それに対応するにはプロパティを作って……。

<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>挨拶した回数 : {{count}}</p>
        <p v-if="isRegulars">いつもありがとうございます</p>
        <p>
            <MyButton label="挨拶する" :greet="greetText" @click="onMyButtonClick"></MyButton>
        </p>
        <p>
            <ResetButton v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

いちいちプロパティを設定するのは面倒です。
もし可能ならこうしたいところです。

<template>
    <div class="home">
        <p>{{greetText}}</p>
        <p>挨拶した回数 : {{count}}</p>
        <p v-if="isRegulars">いつもありがとうございます</p>
        <p>
            <MyButton :greet="greetText" @click="onMyButtonClick">挨拶する</MyButton>
        </p>
        <p>
            <ResetButton v-model="greetText"></ResetButton>
        </p>
    </div>
</template>

そういった要望に応えるのがスロットです。
次のように書けばそれが実現できるでしょう。

src/components/MyButton.vue
<template>
    <button @click="onClick">
        <slot></slot>
    </button>
</template>

クラス属性

ところでボタンの見た目を変えたいとき css のクラスを設定することがあります。
で、ここまでの流れでいくとプロパティで設定するのかな? と考えてしまいそうですが、実はコンポーネントに class を設定すると直下の DOM 要素に class が設定されます。

src/component/Home.vue
<template>
    <div class="home">
        <p>
            <MyButton class='my-button'>挨拶する</MyButton>
        </p>
    </div>
</template>

このように設定した場合MyButtonの直下 DOM 要素(button要素)に class='my-button' が設定されます。

src/component/MyButton.vue
<template>
    <button @click="onClick">
        <slot></slot>
    </button>
</template>

もし button 要素に class が設定されていた場合はマージされるので問題ないです。

src/component/MyButton.vue
<template>
    <button class='primary' @click="onClick"> <!-- この場合は class='my-button primary' になる
        <slot></slot>
    </button>
</template>

これにより必須の class 属性はコンポーネントに定義しておいて、それ以外は上位コンポーネントから設定するといったことができます。

ディレクティブ

v-if というものがすでに一度出てきています。
DOM の操作で利用します。

typescript 特有ではないのですが、よく利用するので簡単に解説します。
詳しいところは vue 公式をご確認ください。

v-if

すでに使用したとおり、その DOM を表示したり非表示したりする用途に使われます。

<p v-if="isRegulars">いつもありがとうございます</p>

これはisRegularsプロパティが true にならない限り DOM 上に現れません。

v-show

v-if と同じような使われ方をします。

<p v-show="isRegulars">いつもありがとうございます</p>

こちらは非表示の際にもデータを保持します。
ですので表示非表示を繰り返すような場合には最適です。

v-for

繰り返し構文です。

繰り返し要素を表示したい場合に使います。
key を設定しないとワーニングが出たりするのでご注意ください。

まとめ

以上で typescript で vue コンポーネントを作るための最低限の知識になるかと思います。

コンポーネントの粒度は人によってまちまちになったりすると思いますが、個人的なお勧めは細かい部分も全てコンポーネントにして、そのコンポーネントをいくつも組み合わせて大きなコンポーネントを作るというやり方がお勧めです。

今のところは typescript で vue を使うと、ページや普通のコンポーネントを作るには問題なく作ることができて、クラスっぽい書き方に慣れてる人には手軽に書けます。
反面、すごく凝ったコンポーネントを作ろうとすると途端に any 祭りになってタイプとはなんぞや状態になったりします。
またデバッガーも完璧にサポートされているとは言い難くて、上手いことブレークポイントを設定できなかったりします(これは自分の設定が悪いかも)。

そんなわけで便利なコンポーネントは割り切って js で作ってしまって、ウェブページは typescript で作るっていう形に私は落ち着いていたりします。

ちまたで流行りの vue.js をやりたいけどクラスや型が捨てきれないという方は是非とも vue.ts を触ってみてはどうでしょうか。
誰かに使ってもらうためにコンポーネントを作るのは楽しいのでお勧めです。

ソース

最終的なソースはこちらに Up してあります。
https://github.com/nrslib/GettingStartedVueJsWithTypescript

細かい Tips

説明で省いた部分(同時に説明すると混乱しそう)をここに記述します。

イベント

イベントの名前

lowerCamel で定義されたイベントのイベントハンドラ登録用のアトリビュート名はケバブケースでも記述可能です。
(前はむしろケバブケース以外はアウトだったんですが変わったようです)

<template>
    <button @click="onClick">MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        /** lowerCamel で定義 */
        @Emit()
        public myClicked(){
        }

        public onClick(){
            this.myClicked();
        }
    }
</script>
<template>
    <div class="home">
        <p>
            <MyButton :greet="greetText" @my-clicked="onMyButtonClick"></MyButton>
        </p>
    </div>
</template>

イベントの定義の省略

イベント名を@Emitアノテーションに記述することでイベント用のメソッド定義を省略することができます。

<template>
    <button @click="onClick">MyButton</button>
</template>

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

    @Component
    export default class MyButton extends Vue {
        @Prop()
        public greet?: string;

        // click() メソッドが不要
        @Emit('click')
        public onClick(){
            alert(this.greet);
        }
    }
</script>

テンプレート

template 直下には

DOM 要素は必ず一つです。
次の構文はエラーが起きます。

<template>
    <h1>Header</h1>
    <p>Content</p>
</template>

こういったときは div 要素で囲む必要があります。

<template>
    <div>
        <h1>Header</h1>
        <p>Content</p>
    </div>
</template>
nrslib
DDD とかアーキテクチャ関連の話が好きです。
https://nrslib.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした