- Vue.js
Vue script setupの書き方|defineProps等マクロ早見表
script setup とは、Vue の単一ファイルコンポーネント(.vue ファイル、以下 SFC)で Composition API を短く書くための構文です。 script タグに setup 属性を付けるだけで、トップレベルに書いた変数・関数・import がそのままテンプレートから使えるようになり、props やイベントは defineProps などの「コンパイラマクロ」で宣言します。
この記事では、7つのマクロ(defineProps / defineEmits / defineModel / defineExpose / defineSlots / defineOptions / withDefaults)を「用途・導入バージョン・型の書き方・最小コード」の早見表にまとめました。掲載したコードは Vue 3.5.43 でコンパイルして動かし、出力とエラー文をそのまま載せています。Vue 3.5 の props 分割代入、ジェネリックコンポーネント、v-model の複数指定と修飾子、Options API との対応表まで1ページで確認できます。
script setup マクロの早見表
マクロは import せずにそのまま書けます。コンパイル時に通常のコンポーネント定義へ変換されるため、実行時には関数として残りません。
| マクロ | 用途 | 使える版 | 型の書き方(TypeScript) | 最小コード |
|---|---|---|---|---|
defineProps |
親から受け取る値を宣言 | 3.2〜 | 型引数にオブジェクト型を渡す | const props = defineProps<{ label: string }>() |
withDefaults |
型で宣言した props に既定値を付ける | 3.2〜 | 第2引数に既定値のオブジェクト | withDefaults(defineProps<Props>(), { title: '無題' }) |
| props 分割代入 | props を変数として取り出し、既定値も書く | 3.5〜 | 分割代入の = で既定値 |
const { count = 0 } = defineProps<{ count?: number }>() |
defineEmits |
親へ送るイベントを宣言 | 3.2〜(タプル記法は 3.3〜) | イベント名: [引数] の形 |
const emit = defineEmits<{ change: [value: number] }>() |
defineModel |
v-model 用の双方向の値を宣言 | 3.4〜 | 型引数に値の型 | const model = defineModel<number>({ default: 0 }) |
defineExpose |
親がテンプレート参照で使える値を公開 | 3.2〜 | 渡したオブジェクトから推論 | defineExpose({ open, close }) |
defineSlots |
スロットとスロット props の型を宣言 | 3.3〜 | 型引数にスロット名と関数型 | defineSlots<{ default(props: { item: T }): any }>() |
defineOptions |
name や inheritAttrs などを指定 |
3.3〜 | 引数はオブジェクト | defineOptions({ inheritAttrs: false }) |
generic 属性 |
ジェネリックコンポーネント | 3.3〜 | script タグの属性に書く |
generic="T extends { id: number }" |
3.3〜・3.4〜・3.5〜の表記は、今回公式の API リファレンス(SFC script setup のページ)を開いて確認しました。3.2〜の行は script setup そのものが正式機能になった版で、一般に知られた仕様として記載しています。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Vue script setupの書き方|defineProps等マクロ早見表」
この記事の検証環境
| 項目 | 版 |
|---|---|
| vue / @vue/compiler-sfc | 3.5.43 |
| @vue/test-utils | 2.5.1 |
| jsdom | 30.1.1 |
| TypeScript / vue-tsc | 5.9.3 / 3.3.12 |
| Node.js | 22.22.2 |
SFC は @vue/compiler-sfc の compileScript でコンパイルし、Node.js 上の jsdom にマウントして動作を確かめました。ブラウザと Vite の開発サーバーでの表示は未実行です。型エラーは vue-tsc --noEmit の出力です。
script setup の基本形をコピペで確認する
まずは props・イベント・v-model を1つずつ持つカウンターです。lang="ts" を付けると型引数で宣言できます。
<!-- Counter.vue -->
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
label: string
step?: number
}>()
const emit = defineEmits<{
change: [value: number]
}>()
const count = defineModel<number>({ default: 0 })
const doubled = computed(() => count.value * 2)
function add() {
count.value += props.step ?? 1
emit('change', count.value)
}
</script>
<template>
<button @click="add">{{ label }}: {{ count }} (x2 = {{ doubled }})</button>
</template>
親から label="在庫"、:step="3"、v-model に 5 を渡してボタンを1回クリックした結果です。
在庫: 5 (x2 = 10)
change イベント: 5
在庫: 8 (x2 = 16) / 親の値 = 8
change イベントの時点で 5 と表示されているのは、defineModel の値は代入した直後ではなく、親が値を更新して props が流れ直したあとに変わるためです。更新後の値をすぐ使いたい場合は、const next = count.value + step のように自分で計算した値を代入と emit の両方に使うと意図どおりになります。
script setup で押さえる4つのルール
| ルール | 内容 |
|---|---|
| トップレベルがそのまま公開される | 変数・関数・import したコンポーネントは、return なしでテンプレートから使える |
| マクロは import 不要 | defineProps などはコンパイラが処理する。ref や computed は通常どおり import する |
| インスタンスごとに実行される | script setup の中身はコンポーネントが作られるたびに実行される。1回だけ実行したい処理は通常の script ブロックに分ける |
| 外には何も公開されない | 親がテンプレート参照で触れるのは defineExpose に渡したものだけ |
Options API と script setup の対応表
Options API で書かれたコンポーネントを読み替えるときの対応です。
| Options API | script setup での書き方 |
|---|---|
props: { ... } |
defineProps() |
emits: [ ... ] |
defineEmits() |
data() |
ref() / reactive() |
computed: { ... } |
computed() |
methods: { ... } |
ふつうの関数宣言 |
watch: { ... } |
watch() / watchEffect() |
mounted() などのフック |
onMounted() などを import して呼ぶ |
components: { ... } |
import するだけ(登録の記述は不要) |
name / inheritAttrs |
defineOptions({ ... }) |
expose: [ ... ] |
defineExpose({ ... }) |
this.$emit('x') |
emit('x')(defineEmits の戻り値) |
this.$refs.x |
useTemplateRef('x')(3.5〜)または同名の ref(null) |
this.$attrs / this.$slots |
useAttrs() / useSlots() |
model + update:modelValue の手書き |
defineModel() |
script setup には this がありません。そのぶん、値の出どころが import と変数宣言だけで追えるようになります。
defineProps:props を宣言する3つの書き方
| 書き方 | コード | 向いている場面 |
|---|---|---|
| 型で宣言(型ベース) | defineProps<{ label: string; step?: number }>() |
TypeScript を使う場合の基本 |
| 実行時オブジェクトで宣言 | defineProps({ label: { type: String, required: true } }) |
JavaScript の場合、validator を使う場合 |
| 配列で宣言 | defineProps(['label', 'step']) |
試作など、型を付けない場合 |
型ベースで書いても、コンパイラが実行時の props 定義を生成します。そのため、必須の props を渡さなかったり型が合わなかったりすると、開発時は次の警告が出ます(label を渡さず、step に文字列 "3" を渡した実行結果)。
[Vue warn]: Missing required prop: "label"
at <Counter step="3" ref="VTU_COMPONENT" >
at <VTUROOT>
[Vue warn]: Invalid prop: type check failed for prop "step". Expected Number with value 3, got String with value "3".
at <Counter step="3" ref="VTU_COMPONENT" >
at <VTUROOT>
型引数と実行時宣言を併用するとコンパイルエラーになる
型引数とオブジェクトの両方を渡すと、コンパイルの段階で止まります。
<script setup lang="ts">
const props = defineProps<{ label: string }>({
label: { type: String, required: true },
})
</script>
[@vue/compiler-sfc] defineProps() cannot accept both type and non-type arguments at the same time. Use one or the other.
ErrBoth.vue
1 | <script setup lang="ts">
2 | const props = defineProps<{ label: string }>({
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3 | label: { type: String, required: true },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 | })
| ^^
defineEmits でも同じです。
[@vue/compiler-sfc] defineEmits() cannot accept both type and non-type arguments at the same time. Use one or the other.
直し方は「どちらか一方に寄せる」です。TypeScript なら型引数だけにして、既定値は次の節の方法で付けます。また、defineProps を1つのコンポーネントで2回呼ぶと duplicate defineProps() call というエラーになるので、props は1つの型にまとめます。
既定値の付け方:withDefaults と props 分割代入(3.5)
型ベースの宣言には default を書く場所がないため、既定値は別の方法で指定します。
withDefaults(3.4 以前からある書き方)
<!-- WithDefaults.vue -->
<script setup lang="ts">
interface Props {
title?: string
tags?: string[]
}
const props = withDefaults(defineProps<Props>(), {
title: '無題',
tags: () => ['未分類'],
})
</script>
<template>
<p>{{ props.title }} / {{ props.tags.join(',') }}</p>
</template>
props なしでマウントした場合と、両方渡した場合の結果です。
無題 / 未分類
議事録 / 会議
配列やオブジェクトの既定値は、インスタンス間で同じ参照を共有しないよう関数で返します。
props 分割代入と既定値(Vue 3.5〜)
Vue 3.5 からは、defineProps の戻り値を分割代入した変数がリアクティブなまま使えます。既定値は JavaScript の分割代入の構文で書けます。
<!-- Destructure.vue -->
<script setup lang="ts">
import { watch, watchEffect } from 'vue'
const { count = 0, label = '件数' } = defineProps<{
count?: number
label?: string
}>()
watchEffect(() => console.log(`[watchEffect] ${label} = ${count}`))
watch(() => count, (v, old) => console.log(`[watch getter] ${old} -> ${v}`))
</script>
<template>
<p>{{ label }}: {{ count }}</p>
</template>
props なしでマウントし、count を 3 に、続けて label を「在庫数」に変えたときの出力です。
[watchEffect] 件数 = 0
件数: 0
[watchEffect] 件数 = 3
[watch getter] 0 -> 3
件数: 3
[watchEffect] 在庫数 = 3
在庫数: 3
変数なのに追従できるのは、コンパイラが count を __props.count に書き換えているからです。コンパイル後のコードを見ると確認できます。
watchEffect(() => console.log(`[watchEffect] ${__props.label} = ${__props.count}`));
watch(() => __props.count, (v, old) => console.log(`[watch getter] ${old} -> ${v}`));
分割代入した変数を watch にそのまま渡すとエラーになる
書き換えの結果、watch(count, ...) は「数値そのもの」を渡すことになるため、コンパイラが先に知らせてくれます。
[@vue/compiler-sfc] "count" is a destructured prop and should not be passed directly to watch(). Pass a getter () => count instead.
ErrWatch.vue
2 | import { watch } from 'vue'
3 | const { count = 0 } = defineProps<{ count?: number }>()
4 | watch(count, (v) => console.log(v))
| ^^^^^
直し方は watch(() => count, ...) とゲッター関数で包むことです。自作のコンポーザブルに渡すときも同じで、useSomething(() => count) の形にします。
分割代入した props へ代入しようとした場合も、コンパイル時に止まります。
[@vue/compiler-sfc] Cannot assign to destructured props as they are readonly.
ErrAssign.vue
2 | const { count = 0 } = defineProps<{ count?: number }>()
3 | function reset() {
4 | count = 0
| ^^^^^
値を変えたいときは、親へイベントを送るか、次に紹介する defineModel を使います。
なお、分割代入と withDefaults を同時に書くと、次の警告が出て分割代入のリアクティブな扱いが無効になります。どちらか一方を選びます。
[@vue/compiler-sfc] withDefaults() is unnecessary when using destructure with defineProps().
Reactive destructure will be disabled when using withDefaults().
Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).
分割代入でリアクティブ性が消える例と直し方
リアクティブなまま使えるのは、defineProps() の戻り値をその場で分割代入した場合だけです。いったん props に受けてから分割代入すると、ふつうの JavaScript の分割代入になり、その時点の値のコピーになります。
<!-- LostReactivity.vue -->
<script setup lang="ts">
import { computed, toRefs } from 'vue'
const props = defineProps<{ count: number }>()
const { count } = props // (A) ふつうの分割代入:その時点の値のコピー
const doubledA = computed(() => count * 2)
const { count: countRef } = toRefs(props) // (B) toRefs:ref として取り出す
const doubledB = computed(() => countRef.value * 2)
const doubledC = computed(() => props.count * 2) // (C) props.count を直接読む
</script>
<template>
<p>A={{ doubledA }} B={{ doubledB }} C={{ doubledC }}</p>
</template>
count を 1 でマウントし、5 に変えたときの結果です。
count=1: A=2 B=2 C=2
count=5: A=2 B=10 C=10
A だけが 2 のまま変わりません。書き方ごとの違いをまとめます。
| 書き方 | props の変更に追従 | 補足 |
|---|---|---|
const { count } = defineProps() |
する(3.5〜) | コンパイラが props.count に書き換える |
const props = defineProps() → props.count |
する | どの版でも使える基本形 |
const { count } = toRefs(props) |
する | count.value で読む |
const { count } = props |
しない | その時点の値のコピーになる |
const n = props.count(トップレベル) |
しない | 初期値として使いたいときだけ |
defineEmits:イベントを型つきで宣言する
// 3.3〜の短い書き方(名前つきタプル)
const emit = defineEmits<{
change: [value: number]
remove: [] // 引数なし
rename: [id: number, name: string] // 引数2つ
}>()
// 関数の呼び出しシグネチャで書く形(3.2 から使える)
const emit2 = defineEmits<{
(e: 'change', value: number): void
}>()
// 実行時宣言(JavaScript でも使える)
const emit3 = defineEmits(['change', 'remove'])
型で宣言しておくと、親が受け取るハンドラの引数の型まで検査されます。型エラーの例は後半の「型エラーを vue-tsc で確認する」に載せました。
defineModel:v-model を1行で受け取る(3.4〜)
defineModel は、props の modelValue とイベント update:modelValue をまとめて宣言し、ref として返します。model.value に代入すると親の値が更新されます。
| 書き方 | 親側の指定 | 意味 |
|---|---|---|
defineModel<number>() |
v-model="n" |
既定の v-model |
defineModel<number>({ default: 0 }) |
v-model="n"(省略可) |
既定値つき |
defineModel<string>('title', { required: true }) |
v-model:title="t" |
名前つき。複数並べられる |
const [model, modifiers] = defineModel<string, 'trim'>() |
v-model.trim="s" |
修飾子を受け取る |
v-model を複数指定し、修飾子も受け取る
<!-- RangeField.vue -->
<script setup lang="ts">
// v-model:min / v-model:max の2つと、v-model:keyword.trim.upper の修飾子
const min = defineModel<number>('min', { required: true })
const max = defineModel<number>('max', { required: true })
const [keyword, keywordModifiers] = defineModel<string, 'trim' | 'upper'>('keyword', {
default: '',
set(value) {
let v = value
if (keywordModifiers.trim) v = v.trim()
if (keywordModifiers.upper) v = v.toUpperCase()
return v
},
})
</script>
<template>
<input class="min" type="number" v-model.number="min" />
<input class="max" type="number" v-model.number="max" />
<input class="kw" v-model="keyword" />
</template>
<!-- RangeParent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import RangeField from './RangeField.vue'
const min = ref(1)
const max = ref(10)
const keyword = ref('')
</script>
<template>
<RangeField v-model:min="min" v-model:max="max" v-model:keyword.trim.upper="keyword" />
<p class="out">{{ min }}〜{{ max }} / [{{ keyword }}]</p>
</template>
入力欄に 3、8、前後に空白を付けた「 vue 」を入れる前と後の、親の表示です。
1〜10 / []
3〜8 / [VUE]
upper は Vue に組み込みの修飾子ではなく、この例で独自に決めた名前です。独自の修飾子は defineModel の2つ目の戻り値で有無を調べ、set オプションで値を加工します(読むときに加工する get もあります)。
defineModel に default を付け、親が v-model を指定しなかった場合、子は既定値、親は undefined という食い違いが起きることがあります。親でも同じ初期値を持つか、required: true にしておくと状態がそろいます。
defineExpose・defineSlots・defineOptions を1つの例で確認する
ダイアログのコンポーネントで3つをまとめて使います。
<!-- Dialog.vue -->
<script setup lang="ts">
import { ref } from 'vue'
defineOptions({ name: 'AppDialog', inheritAttrs: false })
defineSlots<{
default(props: { close: () => void }): any
footer?(props: { opened: boolean }): any
}>()
const opened = ref(false)
const secret = ref('外からは見えない値')
function open() { opened.value = true }
function close() { opened.value = false }
defineExpose({ open, close, opened })
</script>
<template>
<section v-if="opened" class="dialog" v-bind="$attrs">
<slot :close="close" />
<footer><slot name="footer" :opened="opened">既定のフッター</slot></footer>
</section>
</template>
<!-- DialogParent.vue -->
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import Dialog from './Dialog.vue'
const dialog = useTemplateRef('dialog')
defineExpose({ dialog })
</script>
<template>
<button class="opener" @click="dialog?.open()">開く</button>
<Dialog ref="dialog" data-test="d1">
<template #default="{ close }">
<button class="closer" @click="close">閉じる</button>
</template>
</Dialog>
</template>
「開く」を押し、公開された内容を調べ、「閉じる」を押したときの実行結果です。
name: AppDialog / inheritAttrs: false
開く前: "<button class=\"opener\">開く</button>\n<!--v-if-->"
開いた後: <section class="dialog" data-test="d1"><button class="closer">閉じる</button> <footer>既定のフッター</footer> </section>
公開されたキー: [ 'open', 'close', 'opened' ] / secret: undefined / opened: true
閉じた後 section: false
| マクロ | この例での役割 | 結果から読み取れること |
|---|---|---|
defineExpose |
open / close / opened を親に公開 |
公開していない secret は undefined。公開した ref は自動で中身が取り出される(opened: true) |
defineSlots |
スロット props の型を宣言 | 親の #default="{ close }" に型が付く。実行時の動作は変えない(型のための宣言) |
defineOptions |
name と inheritAttrs を指定 |
inheritAttrs: false にして v-bind="$attrs" で渡したので、data-test が section に付いた |
defineOptions には props・emits・expose・slots は書けません。それぞれ専用のマクロで宣言します。
ジェネリックコンポーネント:generic 属性の書き方(3.3〜)
script タグの generic 属性に型引数を書くと、渡された props から型が推論されるコンポーネントを作れます。
<!-- SelectList.vue -->
<script setup lang="ts" generic="T extends { id: number }">
const { items } = defineProps<{ items: T[] }>()
const selected = defineModel<T | null>({ default: null })
const emit = defineEmits<{ pick: [item: T] }>()
function pick(item: T) {
selected.value = item
emit('pick', item)
}
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id" :class="{ on: selected?.id === item.id }" @click="pick(item)">
<slot :item="item">{{ item.id }}</slot>
</li>
</ul>
</template>
items に [{ id: 1, name: '椅子' }, { id: 2, name: '机' }] を渡し、スロットで item.name を表示して2件目をクリックした結果です。
<ul> <li class="">椅子</li> <li class="on">机</li> </ul>
pick: [[{"id":2,"name":"机"}]] update: [[{"id":2,"name":"机"}]]
| 書きたいこと | generic 属性の値 |
|---|---|
| 型引数1つ | generic="T" |
| 制約つき | generic="T extends { id: number }" |
| 複数 | generic="T extends string, U extends Item" |
generic は lang="ts" と組み合わせて使います。props・emit・v-model・スロットのすべてで同じ T が共有されるので、親側では pick イベントの引数やスロットの item に、渡した配列の要素の型がそのまま付きます。
型エラーを vue-tsc で確認する
テンプレートの型検査は vue-tsc で行います。次の親コンポーネントには、わざと合わない指定を4つ入れてあります。
<!-- TypeErr.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import Counter from './Counter.vue'
import SelectList from './SelectList.vue'
const n = ref(0)
const names = ['椅子', '机']
function onChange(value: string) {}
</script>
<template>
<Counter v-model="n" />
<Counter label="在庫" :step="'3'" v-model="n" />
<Counter label="在庫" v-model="n" @change="onChange" />
<SelectList :items="names" />
</template>
npx vue-tsc --noEmit -p tsconfig.json
TypeErr.vue(12,4): error TS2345: Argument of type '{ modelValue: number; }' is not assignable to parameter of type '{ readonly label: string; readonly step?: number | undefined; readonly modelValue?: number | undefined; readonly onChange?: ((value: number) => any) | undefined; readonly "onUpdate:modelValue"?: ((value: number) => any) | undefined; } & VNodeProps & AllowedComponentProps & ComponentCustomProps & Record<...>'.
Property 'label' is missing in type '{ modelValue: number; }' but required in type '{ readonly label: string; readonly step?: number | undefined; readonly modelValue?: number | undefined; readonly onChange?: ((value: number) => any) | undefined; readonly "onUpdate:modelValue"?: ((value: number) => any) | undefined; }'.
TypeErr.vue(13,24): error TS2322: Type 'string' is not assignable to type 'number'.
TypeErr.vue(14,36): error TS2322: Type '(value: string) => void' is not assignable to type '(value: number) => any'.
Types of parameters 'value' and 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
TypeErr.vue(15,16): error TS2322: Type 'string[]' is not assignable to type '{ id: number; }[]'.
Type 'string' is not assignable to type '{ id: number; }'.
| 行 | 原因 | 直し方 |
|---|---|---|
| 12 | 必須の label を渡していない |
label を渡す、または型を label?: string にする |
| 13 | step に文字列を渡した |
:step="3" と数値で渡す |
| 14 | change のハンドラの引数が string |
defineEmits の宣言に合わせて number にする |
| 15 | generic の制約 { id: number } に合わない配列 |
id を持つオブジェクトの配列を渡す |
1行目のエラー文から、defineModel と defineEmits が props 側に modelValue・onUpdate:modelValue・onChange として現れていることも読み取れます。マクロが何を生成しているかを知る手がかりになります。
エラー文から直し方を引く逆引き表
この記事で再現したメッセージの一覧です。
| メッセージ(抜粋) | 出る場面 | 直し方 |
|---|---|---|
defineProps() cannot accept both type and non-type arguments at the same time |
型引数と実行時宣言を併用 | どちらか一方にする |
defineEmits() cannot accept both type and non-type arguments at the same time |
同上(emits) | どちらか一方にする |
duplicate defineProps() call |
defineProps を2回呼んだ |
1つの型にまとめる |
"count" is a destructured prop and should not be passed directly to watch() |
分割代入した props を watch に直接渡した |
() => count で渡す |
Cannot assign to destructured props as they are readonly. |
分割代入した props に代入した | emit か defineModel で親に更新してもらう |
withDefaults() is unnecessary when using destructure with defineProps() |
分割代入と withDefaults の併用 |
分割代入の既定値に寄せる |
Missing required prop: "label" |
必須 props を渡していない(実行時) | 親で渡す、または任意にして既定値を付ける |
Invalid prop: type check failed for prop "step" |
型の違う値を渡した(実行時) | :step="3" のように : を付けて数値で渡す |
よくある質問
Q. script setup と通常の script は同じファイルに書けますか?
A. 書けます。通常の script ブロックはモジュールの読み込み時に1回だけ実行されるので、名前つき export や、1回だけ行いたい初期化を置く場所として使えます。2つのブロックの lang はそろえます。name や inheritAttrs を指定したいだけなら、defineOptions で script setup の中に書けます。
Q. props の既定値は withDefaults と分割代入のどちらで書けばよいですか?
A. Vue 3.5 以上なら分割代入の既定値が短く書けます。3.4 以前にも対応させるコードや、props.xxx の形で統一したいコードでは withDefaults が合います。両方を同時に書くと警告が出るので、コンポーネントごとにどちらかへ寄せます。
Q. defineModel と、defineProps+defineEmits の組み合わせはどう使い分けますか?
A. 「親の値を子から書き換える」用途は defineModel が向いています。modelValue と update:modelValue の宣言が1行にまとまります。子からは通知だけを送り、値をどうするかは親に任せたい場合は、defineProps と defineEmits の組み合わせがわかりやすくなります。
Q. defineProps の中でローカル変数を参照できますか?
A. マクロの引数は setup の外へ移されてコンパイルされるため、setup 内で宣言した変数に依存する書き方はコンパイルエラーの対象になります(公式の API リファレンスに記載。import した値は参照できます)。今回の確認では、const fallback = 10 のようなリテラルの定数はエラーにならずコンパイルできました。既定値に使う値は、import するかリテラルで書くと確実です。
まとめ
- script setup は、トップレベルの変数・関数・import がそのままテンプレートで使える構文。props やイベントは import 不要のマクロで宣言する
- props は
definePropsの型引数で宣言。型引数と実行時宣言の併用はコンパイルエラーになるので、どちらか一方にする - 既定値は、Vue 3.5 以上なら分割代入(
const { count = 0 } = defineProps())、それ以前はwithDefaults - 分割代入した props を
watchやコンポーザブルに渡すときは() => count。const { count } = propsはその時点のコピーになり追従しない - v-model は
defineModel。名前を付ければ複数並べられ、修飾子は2つ目の戻り値とsetで扱う - 親に公開するのは
defineExposeに渡したものだけ。スロットの型はdefineSlots、nameとinheritAttrsはdefineOptions generic属性で、props・emit・スロットに同じ型引数を通せる。型の検査はvue-tsc --noEmitで行う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Vue script setupの書き方|defineProps等マクロ早見表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。