【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つのファイルに書きます。
<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なども利用できます。
下の記事に、webpackを利用してvueファイルでscssを使うためのビルド設定を載せています。
script
タグの中には、コンポーネントのスクリプトを書きます。template
が無い以外はcomponentの中身と同じですが、必ずexport
します。基本的にはexport default
で問題ありません。
ここで、template
タグの直下に複数の子要素を置くことはできません。
<template> <div> <ButtonComponent color="yellow" size="large"></ButtonComponent> </div> <div> <p>fuga</p> </div> </template>
<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
するところでは
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"); });
のようにします。require
かimport
で先ほど作成したコンポーネントを読み込んだ後、createApp
内のcomponents
に指定します。components
は連想配列です。
ビルド設定によっては、'./button.vue'
のように拡張子を付ける必要がある場合があります。
使うときは、前回と同様に使います。ここで、ButtonComponent
のようなキャメルケースの場合、button-component
のようにケバブケースに直すのを忘れないように注意する必要があります。
<div id="app"> <button-component></button-component> </div>
ビルドしてindex.htmlを開き、このように表示されれば成功です。

コンポーネントのCSS
シングルファイルコンポーネントでは、HTML、CSS、Scriptの3つにパーツが分かれています。
このとき、CSSではそのコンポーネント限定でスタイルを適用できる方法があり、scoped
とmodules
の2種類あります。
scoped
scopedでは、コンポーネントに書いたCSSの影響範囲を、そのコンポーネント内に限定することができます
<template> <!-- ... --> </template> <style scoped> p { color: #f00; } </style> <script> // ... </script>
適当にコンポーネントをもう一つ作って表示してみると普通に表示されますが、開発者ツールを見ると属性が付与されていることが分かります。

このように付与される属性のおかげで、styleの影響をそのコンポーネント内だけに留めているということです。
ここで、外で普通に指定したCSSは普通に適用されます。あくまで属性が付与されているだけで、classやidなどはそのままであるためです。
module
webpackでビルド時に使用する際は、css-loaderの設定が必要です。
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はこのようになっています。

ランダムなクラス名がついていることが分かります。
また、要は単に変数に入った文字列であるため、普通のクラスの場合と同じように使用できます。
<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
のように、$
なしでアクセスできます。
しかし、data
やmethod
などとの名前の重複や$style
という名前を考えると、$bbb
のように$
を付けたほうが良いと思われます。
応用例
moduleでは、外部のクラスに影響されず、変数を用いてクラスにアクセスします。その御蔭で、外部から名前を指定しやすいという特徴があります。
<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>
<!-- $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自体は今までのコンポーネントの書き方と同じような書き方で使えるため、すぐに慣れると思います。
