【vue】 1つのvueファイルにコンポーネントを定義する方法

普通にコンポーネントを書くと、app.component(...)と書きました。しかし、このままではtemplateは非常に書きづらく、入力補完やハイライトもうまく効きませんし、styleを書いた場合、外側に影響が出てしまいます。

そこで、コンポーネントはしばしば1つの.vueファイルに書きます。このようなコンポーネントをシングルファイルコンポーネントと呼びます。

webpackなどのモジュールバンドラーを使用していること前提になります。

シングルファイルコンポーネント (SFC) の書き方

Vue.jsでのシングルファイルコンポーネントは、DOM、CSS、スクリプトの3つのパーツを1つのファイルに纏め、この3つがまとまったものを1つのコンポーネントとして扱うものです。 従来のコンポーネントと比べて、CSSをコンポーネント外に影響させないようにする、DOMの補完が効くなどといった利点があります。

1つのシングルファイルコンポーネント内では、template、style、scriptの3つのパーツに分解され、この3つを1つのファイルに書きます。

button.vue
<template>
    <div>
        <p>{{ msg }}</p>
    </div>
</template>

<style scoped>
p {
    color: #f00;
}
</style>

<script>
export default {
    data() {
        return {
            msg: "Hello, sfc World!!;",
        }
    },
}
</script>

templateタグの中には、そのコンポーネントで表示したいHTMLを書きます。

styleタグの中には、適用したいcssを書きます。loaderを整えれば、scssやsassなども利用できます。

scriptタグの中には、コンポーネントのスクリプトを書きます。templateが無い以外はcomponentの中身と同じですが、必ずexportします。基本的にはexport defaultで問題ありません。

ここで、templateタグの直下に複数の子要素を置くことはできません。

NGな例 (templateの子要素が2つ)
<template>
    <div>
        <ButtonComponent color="yellow" size="large"></ButtonComponent>
    </div>
    <div>
        <p>fuga</p>
    </div>
</template>
OKな例
<template>
    <div>
        <div>
            <ButtonComponent color="yellow" size="large"></ButtonComponent>
        </div>
        <div>
            <p>fuga</p>
        </div>
    </div>
</template>

SFCを使う

コンポーネントを使う際は、先ほど書いたコンポーネントをimportします。

コンポーネントのファイル名をbutton.vueとし、このようなディレクトリ構成の場合、

project\
├ dist\
│ └ index.html
├ src\
│ ├ button.vue
│ └ app.js
├ webpack.config.json
└ package.json

vueでcreateAppするところでは

app.js
import * as Vue from "vue";

// または, const ButtonComponent = require("./button").default;
import ButtonComponent from "./button";

const app = Vue.createApp({
    components: {
        ButtonComponent,
        // ButtonComponent: ButtonComponent, と同じ
    },
    data() {
        return {}
    },
});

document.addEventListener("DOMContentLoaded", () => {
    app.mount("#app");
});

のようにします。requireimportで先ほど作成したコンポーネントを読み込んだ後、createApp内のcomponentsに指定します。componentsは連想配列です。

ビルド設定によっては、'./button.vue'のように拡張子を付ける必要がある場合があります。

使うときは、前回と同様に使います。ここで、ButtonComponentのようなキャメルケースの場合、button-componentのようにケバブケースに直すのを忘れないように注意する必要があります。

<div id="app">
    <button-component></button-component>
</div>

ビルドしてindex.htmlを開き、このように表示されれば成功です。

vue sfc hello world

コンポーネントのCSS

シングルファイルコンポーネントでは、HTML、CSS、Scriptの3つにパーツが分かれています。

このとき、CSSではそのコンポーネント限定でスタイルを適用できる方法があり、scopedmodulesの2種類あります。

scoped

scopedでは、コンポーネントに書いたCSSの影響範囲を、そのコンポーネント内に限定することができます

<template>
<!-- ... -->
</template>

<style scoped>
p {
    color: #f00;
}
</style>

<script>
// ...
</script>

適当にコンポーネントをもう一つ作って表示してみると普通に表示されますが、開発者ツールを見ると属性が付与されていることが分かります。

scopedを使用したときの属性の例

このように付与される属性のおかげで、styleの影響をそのコンポーネント内だけに留めているということです。

ここで、外で普通に指定したCSSは普通に適用されます。あくまで属性が付与されているだけで、classやidなどはそのままであるためです。

module

moduleを指定すると、そのCSSの範囲をコンポーネント内に限定できるだけでなく、class名も自動生成された名前になります。

使用する際は、<p :class="$style.hoge">{{ msg }}</p>のように、v-bindしつつ、$style.クラス名のようにします。

<template>
    <div>
        <p :class="$style.hoge">{{ msg }}</p>
    </div>
</template>

<style module>
.hoge {
    color: #f00;
}
</style>

<script>
export default {
    data() {
        return {
            msg: "Hello, sfc World!!;",
        }
    },
    methods: {
        hoge() {
            // クラス名を取得可能
            console.log(this.$style.hoge);
        }
    }
}
</script>

クラス名をケバブケースにしてしまうと$style["hoge-fuga"]のように[]でアクセスする必要があるため、スネークケースかキャメルケースが望ましいです。

この時、DOMはこのようになっています。

module style example

ランダムなクラス名がついていることが分かります。

また、要は単に変数に入った文字列であるため、普通のクラスの場合と同じように使用できます。

<p class="fuga" :class="{[$style.hoge]: true}">{{ msg }}</p>
<p :class="[$style.hoge, $style.foo, $style.bar]">{{ msg }}</p>

:classでは今までと同様に連想配列で記述できます。キー名には[var]: 値という、Javascriptの連想配列の初期化時に、変数の値をキーとする書き方を行うとうまく動作します。

1つのプロジェクト内でscopedとmoduleを両方使うことは何もしなくても可能です。

ただ、1つのシングルファイルコンポーネント内で両方使おうとしても、全部module扱いになります。

カスタム注入名

通常は、値を取得する際には$styleを用いますが、この名前を変更することができます。

<template>
    <div>
        <p :class="[aaa.foo, $bbb.bar]">{{ msg }}</p>
    </div>
</template>

<style module="aaa">
.foo {
    color: #f00;
}
</style>

<style module="$bbb">
.bar {
    background-color: #000;
}    
</style>

カスタム注入名では、その注入した値自体が変数名になります。module="aaa"なら、クラスはaaa.fooのように、$なしでアクセスできます。

しかし、datamethodなどとの名前の重複や$styleという名前を考えると、$bbbのように$を付けたほうが良いと思われます。

応用例

moduleでは、外部のクラスに影響されず、変数を用いてクラスにアクセスします。その御蔭で、外部から名前を指定しやすいという特徴があります。

button.vue
<template>
    <div>
        <p :class="[$color[color], $size[size]]">{{ msg }}</p>
    </div>
</template>

<style module="$color">
.red {
    color: red;
}
.blue {
    color: blue;
}
</style>

<style module="$size">
.large {
    font-size: 32px;
}
.small {
    font-size: 10px;
}
</style>

<script>
export default {
    props:{
        color: String,
        size: String,
    },
    data() {return {}},
}
</script>
index.html
<!-- $color.blueと、$size.largeを使う -->
<button-component color="blue" size="large"></button-component>

このように、外部からクラス名をpropsで受け取ることで、どのクラスを使うかを外から簡単に制御できます。エラーメッセージなど、状況によって見た目だけが変わるような場合に最適です。

sassやscssを使う

sassやscssを使用したい場合、ビルドの設定を整えた上でlang="scss"などを付けると動作します。

vue-cliで導入した場合、npm i -D sass sass-loaderする必要があります。

<style module lang="scss">
.hoge {
    background-color: #000;
    .foo.bar {
        color: #f00;
    }
}
</style>

まとめ

非常に小規模なものを除いて、基本的には使うことになります。webpackでもvue-cliでもどちらでも良いので環境を整備し、シングルファイルコンポーネントを扱えるようにしておきましょう。

SFC自体は今までのコンポーネントの書き方と同じような書き方で使えるため、すぐに慣れると思います。

vue sfc thumb

役に立ったらシェアしよう!