4. SFC 1
Vue のコンポーネントは、SFC(Single File Component) と呼ばれる拡張子 .vue のファイルで記述します。React が「TypeScript の中に HTML を書く(TSX)」というアプローチなのに対し、Vue は「1 つのファイルの中に TypeScript・HTML・CSS を並べて書く」というアプローチです。Day 1 で HTML ファイルの中に <script> や <style> を書いたのと似ていますね。
本章では、基礎となる SFC の記述を述べます。繰り返しなどの追加で必要な構文は 6 章で取り上げます。
4.1 コンポーネントの定義
SFC は、次の 3 つのブロックから構成されます。
<script setup lang="ts">— コンポーネントのロジック(TypeScript)<template>— コンポーネントの見た目(HTML ベースのテンプレート)<style scoped>— コンポーネントのスタイル(CSS、8 章で扱います)
1 章で取り上げた Thumbnail コンポーネントのプログラムをもう一度見てみましょう(style ブロックは省略しています)。
Thumbnail.vue
<script setup lang="ts">type Props = { srcUrl: string; title: string; description: string;};
defineProps<Props>();</script>
<template> <div class="thumbnail"> <img :src="srcUrl" /> <div class="inner"> <h2>{{ title }}</h2> <p>{{ description }}</p> </div> </div></template>script ブロックには、このコンポーネントで使う変数や関数を普通の TypeScript として書きます。lang="ts" は「中身は TypeScript です」という指定です。setup という属性は、Composition API と呼ばれる Vue 3 の機能を簡単に使うための構文です1。本教材(と Twin:te のフロントエンド)では、常に <script setup lang="ts"> で書きます。おまじないとして毎回付けてください。
template ブロックには、このコンポーネントの見た目を HTML で書きます。このコンポーネントの利用は簡単で、別のコンポーネントの script ブロックで import して、template の中で次のように記述するだけです(ただしコンポーネント名は大文字で始める必要があります)。
App.vue
<script setup lang="ts">import Thumbnail from "./components/Thumbnail.vue";</script>
<template> <div> <Thumbnail src-url="https://user0514.cdnw.net/shared/img/thumb/elly20160701425018_TP_V.jpg" title="のどかな風景" description="あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。" /> </div></template>4.1.1 ルート要素
React では、コンポーネントは複数の HTML 要素を並列で返すことができず、フラグメント(<> ~ </>)と呼ばれる機能でそれらを囲む必要がありました。一方、Vue 3 の template は複数のルート要素をそのまま並べることができます。
<template> <div><p>aaa</p></div> <p>abc</p></template>4.1.2 予約語
React の TSX では、class や for といった属性名が JavaScript(TypeScript)の予約語と衝突するため、className、htmlFor と書き換える必要がありました。一方、Vue の template は HTML そのものなので、class も for もそのまま使うことができます。
4.2 コンポーネント間の値の受け渡し - props
先の Thumbnail の例をもう一度観察してみましょう。script ブロックの冒頭で型 Props が宣言され、defineProps<Props>() と書かれています。
<script setup lang="ts">type Props = { srcUrl: string; title: string; description: string;};
defineProps<Props>();</script>これだけで、型に含まれる各プロパティが template の中でそのまま変数として使えるようになります。defineProps は Vue が用意している特別な関数(コンパイラマクロ)なので、import は必要ありません。
また、Thumbnail コンポーネントを利用する App.vue のプログラムを観察してみると、コンポーネントに値を渡すときには属性を用いて渡すことができることが分かります。
このように、コンポーネントが受け取る値を props と呼びます。props は親コンポーネントから子コンポーネントへ一方向へ流れるデータで、props が変更されるとそれを受け取る子コンポーネントの表示が切り替わります(子コンポーネント側で props の値を書き換えることはできません)。
いくつか注意点があります。
srcUrlのようなキャメルケースの props は、利用する側のテンプレートではsrc-urlのようにケバブケースで書くのが慣習です。- 文字列をそのまま渡すときは普通の属性と同じ書き方で OK です。数値や変数など、式を渡したいときは
:title="someVariable"のように:を付けます(詳しくは次節で説明します)。
4.3 式の表示
式の表示の説明に入る前に、式とは何かについてを理解しておく必要があります。式と言われて皆さんが想像するものは、おそらく 1 + 1 のようなものでしょう。確かにこれも式の一つで、正解です。
ただ、例えば 1 + (2 + 3) や 1 + (2 + (3 + 4))、1 + value(value は変数)はどうでしょうか。JavaScript のフェーズで学んだように、これらも妥当な式です。式がネストしても、式の中に変数が来ても、それらは全て計算可能な式です。
では 1 や value ではどうでしょうか。式と言えるでしょうか。
答えは yes です。式は値が + などの演算子で結合されている必要はなく、1 つの項(単項)からなるものも立派な式です(専門的には + などの演算子で結合された式を二項式と呼びます)。この事実を認めなければ、-1 などといった単項演算子の存在を説明することができません。また、value が式であることが分かったので、変数と置き換えることができる関数呼び出し(testFunc() など)や null、true といったリテラルも式であると結論づけることができます。
このような事実から、以下では変数や 1 + 1 などの記述をまとめて式と呼んでいます。
さて、Vue の template の内部では式を {{ }} で囲って要素の値の部分に配置することで、ある値を表示することができます(この記法をマスタッシュ構文と呼びます)。先のサムネイルの例では <h2>{{ title }}</h2> などの記述によって受け取った値を HTML の値として出力しています。型は string ですが、number や boolean といった値なども表示することができます。変数だけでなく、{{ title.toUpperCase() }} や {{ 1 + 1 }} のような式も書けます。
一方、属性に式を指定するには、:src="srcUrl" のように属性名の前に : を付けて、属性値の部分に式を書きます(: がないとただの文字列 "srcUrl" になってしまいます)。この : や v- から始まる特別な属性を Vue ではディレクティブと呼びます2。
さらに属性には、クリックなどのイベントに反応するイベントハンドラを指定することもできます。こちらは Vue ではよく使う構文なので、次章で確認してみましょう。
4.4 演習
- sample-app の
src/componentsディレクトリを確認して下さい(2 章で中身だけを削除したものです)。今後の演習で作成するコンポーネントはその下に作成していきます。 src/components/Tweet.vueを作成し、Thumbnail のプログラムを参考にして string 型のnameと string 型のtextを受け取るコンポーネントを作成して下さい。コンポーネントの本体はnameがh3、textがp、そしてそれらをdivで囲ったものとして下さい。App.vueで作成したTweetコンポーネントを呼び出し、表示を確認して下さい(開発サーバbun run devを止めていた人は起動し直しましょう)。