みなさん、こんにちは。
素敵なコーディングライフをお過ごしでしょうか?
本日は、すっかりフロントエンド開発のスタンダードになってきた、JavaScriptフレームワークから、Vue.jsを取り上げてみようと思います。
その中でも、話題沸騰中の(?)Composition API のシンタックスシュガーである、<script setup>
を、公式ドキュメントを参考に触ってみようと思います。 Composition APIそのものについてはドキュメントをご覧ください。
気になっていたけど、まだ試していない…という方は是非、一緒にトライしてみましょう〜!
SFC内でComposition APIを利用する際に用いる、シンタックスシュガーです。
ドキュメントでも、「SFC と Composition API の両方を使うならば、おすすめの構文です。」とありますので、積極的に活用していきたいですね。
利点としては、下記が挙げられています。
・ボイラープレートが少なくて、より簡潔なコード
・純粋な TypeScript を使ってプロパティと発行されたイベントを宣言する機能
・実行時のパフォーマンスの向上(テンプレートは中間プロキシなしに同じスコープ内のレンダリング関数にコンパイルされます)
・IDE で型推論のパフォーマンス向上(言語サーバがコードから型を抽出する作業が減ります)
どれも感動的な利点ですが、個人的には特にTypeScriptを導入しやすくなったという面で利点を感じています。
ここからは、ドキュメントに沿って触ってみようと思います。
まずは、基本構文ですが、次のようになります。
<script setup>
console.log('インスタンスが生成されるたびに呼ばれます。')
</script>
注意点としては、内部のコードは、コンポーネントのインスタンスが生成されるたびに呼ばれるということです。setup()
と同様の振る舞いということですね。
一度しか実行されてはいけない場合はどうするか、という問題がありますが、これについては後述いたします。
内部(トップレベル)に宣言した変数や関数はテンプレートで使えます。
<script setup>
const counter = 0;
const countUp = () => {
counter++;
}
</script>
<template>
<div>{{ counter }}</div>
<button @click="countUp">Count Up!</button>
</template>
さらに、import
されたものについても同様です。
<script setup>
import { importedFunc } from "./helpers";
</script>
<template>
<button @click="importedFunc">
インポートされた関数を実行
</button>
</template>
これは、コンポーネントを呼び出す際も同様です。
<script setup>
import BaseButton from "BaseButton.vue";
</script>
<template>
<BaseButton />
</template>
ただし、<component>
タグを利用して動的にコンポーネントを利用する場合は、注意が必要です。import
されたコンポーネントは変数ですので、:is=""
のように、bindする必要があります。
<component :is="MyComponent"></component>
上記の例のcounter
変数はただ宣言しただけなので、これではカウントアップしてもビューに反映されません。
リアクティブにする方法は、通常のComposition APIと同様で、ref
を利用します。
<script setup>
const counter = ref(0); // リアクティブになった!
const countUp = () => {
counter++;
}
</script>
<template>
<div>{{ counter }}</div>
<button @click="countUp">
Count Up!
</button>
</template>
このとき、テンプレートではcounter.value
としてアンラップしないことに注意してください。
テンプレートで参照される際に自動でアンラップされます。
※ script内では手動でアンラップする必要があります。
これらを宣言するには、それぞれ、defineProps
, defineEmits
を使用する必要があります。
引数に、props
, emits
を設定する場合と同様の方法で値を設定します。
これらは<script setup>が処理される際にコンパイルされるため、インポート不要のようです。
<script setup>
// Propsの宣言
const props = defineProps({
foo: String
});
// Emitの宣言
const emit = defineEmits(['event1', 'event2']);
</script>
TypeScriptを利用する場合は、純粋に型宣言することもできます!(嬉)
<script setup>
type PropsType = {
foo: string,
bar?: number
};
type EmitType = {
(e: 'change', id: number): void
(e: 'update', value: string): void
};
// Propsの宣言
const props = defineProps<PropsType>();
// Emitの宣言
const emit = defineEmits<EmitType>()
</script>
ただし、type等をエクスポートする際は、<script>を別に設置する必要があるため注意です。
また、setup
が設定されていないscript
は、インスタンスが生成されるたびに実行されないため、前述した、「一度しか実行してはいけないもの」もこの中に記述できます。
<script>
export type MyType = ...
</script>
<script setup>
// ...
</script>
そして、現在確実に静的解析を行えるものは次の2パターンに限られているようです。
・型リテラル
・同一ファイル内のインタフェースか型リテラルへの参照
従って、他ファイルからインポートされた型については対象外ということになります。
2022年7月15日現在、「今後対応する事も理論的には可能」とありますので、もしかしたらアップデートで対応されるかもしれませんね。
上記のdefineProps<PropsType>()
では、初期値の設定ができません。
初期値も設定したい場合はwithDefaults
も使って、次のようにします。
<script>
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
</script>
いかがでしたでしょうか。ほぼドキュメントと同じことしか書いていませんが…。
僕自身もこのAPIの使用経験が少ないので、しばらく使った感想などあればまた取り上げられればと考えています。
とても便利なものなので、使わない手はないですね!
山口県生まれ、山口県育ち。超インドアなので外出することは少なめですが、Webを身近に感じていただける技術トピックなどを中心にご紹介できればと思っています。よろしくお願いいたします!
REC.