はじめに
※この記事は「LTSグループAdvent Calendar 2023」に参加しています
はじめまして!株式会社エル・ティー・エスでエンジニアとして働いているc-tyan410と申します。
Vue.jsをプロジェクトで使って、早一年が経ちました。
Vue.js 3.2からVue.jsを使い始めたので、最初から<script setup>
構文を使っていました。
本記事では、<script setup>
構文の書き方をまとめていきます。
<script setup>
構文とは
<script setup>
は単一ファイルコンポーネント(SFC)内でCompositionAPIを使用するコンパイル時の糖衣構文です。公式もおすすめしています。
下記のようなメリットがあります。
- ボイラープレートが少なくて、より簡潔なコードになります
- propsとemitを定義する際に純粋なTypeScriptの構文が使えます
- ランタイムのパフォーマンスが向上します
- IDEのパフォーマンスが向上します
基本の構文
この構文を導入するには、setup属性を<script>
ブロックに追加します。
<script setup>
console.log('Hello vue')
</script>
<script setup>
構文はCompositionAPIにおけるsetup()関数内部を<script>
直下に直接記述することができるという構文です。通常の<script>
とは違って、コンポーネントが最初にインポートされたときに一度だけ実行されるのではなく、<script setup>
内のコードはコンポーネントのインスタンスが作成されるたびに実行されます。
変数、関数宣言、インポート
<script setup>
を使用する場合、<script setup>
内で宣言された変数、関数宣言、インポート等はテンプレートで直接使用できます。
- Option API
<script>
export default {
data() {
return {
msg: 'Hello!'
};
},
methods: {
log() {
console.log(this.msg);
}
}
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
<script setup>
<script setup>
// 変数
const msg = 'Hello!'
// 関数
const log = () => {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
インポートも同様に、インポートされたヘルパー関数をmethodsオプションで公開することなくテンプレート内の式で直接使用できます。
- Option API
<script>
import { capitalize } from './helpers';
export default {
methods: {
capitalize
}
}
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
<script setup>
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
リアクティビティー
リアクティブな状態はリアクティビティーAPIを使って明示的に作成する必要があります。setup()関数から返された値と同じように、テンプレート内で参照されるときに自動的にアンラップされます。
- Option API
<template>
<button @click="incrementCount"> {{ count }} </button>
<p>{{ item.orange }}</p>
</template>
<script>
export default {
data() {
return {
count: 0,
item: {
apple: 1,
orange: 2,
banana: '1本'
}
};
},
methods: {
incrementCount() {
this.count++;
}
}
};
</script>
<script setup>
<script setup>
import { ref, reactive } from 'vue'
//refオブジェクト
const count = ref(0)
// 単一プロパティーである.valueを持っています
console.log(count.value)
//reactiveオブジェクト
const item = reactive({
apple: 1,
orange: 2,
banana: '1本'
})
// 1
console.log(item.apple)
</script>
<template>
<button @click="count++">{{ count }}</button>
<p>{{ item.orange }}</p>
</template>
v-bind, v-on, v-model, v-for, v-if
v-for,v-on,v-model,v-for,v-ifはtemplate内で使います。
<script setup>
構文を使う場合も使わない場合もtemplateの内容は変わらないので、
v-for,v-on,v-model,v-for,v-ifは従来通りに使えます。
※v-onの省略記法は@
、v-bindの省略記法は:
です
<script setup>
<script setup>
import { ref, reactive, computed } from 'vue'
// リアクティブなデータプロパティ
const items = reactive(['Apple', 'Banana', 'Orange']) // 表示するアイテムのリスト
const isVisible = ref(false) // アイテムリストの表示状態を制御する
// アイテムリストの表示/非表示を切り替える関数
const toggleVisibility = () => {
isVisible.value = !isVisible.value
}
</script>
<template>
<!-- v-modelディレクティブを使用して、input要素とisVisibleリアクティブ変数を双方向バインド -->
<input v-model="isVisible" type="checkbox" />
<!-- @clickイベントリスナーでボタンクリック時の処理を定義 -->
<button @click="toggleVisibility">
{{ isVisible ? 'Hide' : 'Show' }} Items
</button>
<!-- v-ifディレクティブを使用して、isVisibleがtrueの場合にのみ段落を表示 -->
<p v-if="isVisible">
{{ fruits }}
</p>
<!-- v-forディレクティブでitems配列の各要素に対してli要素を生成 -->
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item }}
</li>
</ul>
</template>
computed()
関数の中身が変化した時に、呼び出されるような関数を作りたい場合に使います。
リアクティビィーと同様に(こちらもリアクティビィーAPIですが)明示的に書く必要があります。
例:下記は2倍の数字を算出するコードです
- Option API
<script>
export default {
data() {
return {
counter: 0
};
},
computed: {
// computedを使用してカウンターの2倍の値を計算
doubledCounter() {
return this.counter * 2;
}
},
methods: {
// カウンターを増加させるメソッド
increment() {
this.counter++;
}
}
};
</script>
<template>
<div>
<h1>カウンター: {{ counter }}</h1>
<h2>2倍の値: {{ doubledCounter }}</h2>
<button @click="increment">増加</button>
</div>
</template>
<script setup>
<script setup>
import { ref, computed } from 'vue';
// refを使用してリアクティブなカウンター変数を作成
const counter = ref(0);
// computedを使用してカウンターの2倍の値を計算
const doubledCounter = computed(() => counter.value * 2);
// カウンターを増加させるメソッド
const increment = () => {
counter.value++;
};
</script>
<template>
<div>
<h1>カウンター: {{ counter }}</h1>
<h2>2倍の値: {{ doubledCounter }}</h2>
<button @click="increment">増加</button>
</div>
</template>
watch, watchEffect
リアクティブな状態の一部が変更されるたびにコールバックを実行する関数です。
watchとwatchEffectの違いは、初期表示の際に実行されるか否かです。
watch:初期表示の際に実行されません({ immediate: true }をつければ、初期表示でも実行されます。)
watchEffct:初期表示の際に実行されます。
例:以下のコードは、名前を変更した回数をカウントするコードです。
- Option API
<script>
import { ref, watch, watchEffect } from 'vue';
export default {
data() {
return {
name: '',
nameChangeCount: 0
};
},
watch: {
// watchオプションを使用してnameの変更を監視
name(newName, oldName) {
console.log(`名前が ${oldName} から ${newName} に変わりました。`);
this.nameChangeCount++;
}
},
mounted() {
// watchEffectを使用してリアクティブな状態の変更を監視
watchEffect(() => {
console.log(`現在の名前は ${this.name} です。`);
});
}
};
</script>
<template>
<div>
<input v-model="name" placeholder="名前を入力">
<p>名前: {{ name }}</p>
<p>名前の変更回数: {{ nameChangeCount }}</p>
</div>
</template>
<script setup>
<script setup>
import { ref, watch, watchEffect } from 'vue';
const name = ref('');
const nameChangeCount = ref(0);
//watchを使用してnameの変更を監視
watch(name, (newValue, oldValue) => {
console.log(`名前が ${oldValue} から ${newValue} に変わりました。`);
nameChangeCount.value++;
});
//watchEffectを使用してリアクティブな状態の変更を監視
watchEffect(() => {
console.log(`現在の名前は ${name.value} です。`);
});
</script>
<template>
<div>
<input v-model="name" placeholder="名前を入力">
<p>名前: {{ name }}</p>
<p>名前の変更回数: {{ nameChangeCount }}</p>
</div>
</template>
コンポーネント
今までcomponentsにimportしたコンポーネントの一覧を登録しなければいけなかったのですが、<script setup>
のスコープ内の値は、カスタムコンポーネントのタグ名としても直接使用できるようになっています。
- Option API
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
}
};
</script>
<template>
<MyComponent />
</template>
<script setup>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
<template>
<ChildComponent />
</template>
ケバブケースの<my-component>
も同じようにテンプレートで動作しますが、公式は一貫性を持たせるためにパスカルケースを強く推奨しています。
defineProps() & defineEmits()
完全な型推論のサポートつきでpropsとemitsのようなオプションを宣言するために、definePropsとdefineEmitsのAPIを使用できます。
これらは<script setup>
の中で自動的に利用できるようになっています。
template内で、propsを使う場合は頭にpropsをつけなくても直説使うことができます。
親→子(defineProps())
<script setup>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
<template>
<ChildComponent message="こんにちは"/>
</template>
<script setup>
const props = defineProps({
message {
type: String,
required: true,
}
});
// script内で使う時には、propsを頭につける
console.log(props.message)
</script>
<template>
<!-- template内で使う時には、propsを頭につけなくても良い -->
<div>
<p>受け取ったメッセージ: {{ message }}</p>
</div>
</template>
子→親(defineEmits())
<script setup>
<script setup>
// 親コンポーネントではdefineEmitsで定義するイベントをキャメルケースで書く
const emit = defineEmits(['eventFromChild']);
// イベントを送出する関数
const sendEvent = () => {
emit('eventFromChild', '子コンポーネントからのデータ');
};
</script>
<template>
<button @click="sendEvent">イベントを送出</button>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
// 子コンポーネントから受けっと値が引数で入ってくる
const handleEvent = (data) => {
console.log(data)
}
</script>
<template>
<ChildComponent @event-from-child="handleEvent"/>
</template>
- defineProps() & defineEmits()はインポートする必要はなく
<script setup>
が処理されるときにコンパイルされます。 - definePropsとdefineEmitsは、渡されたオプションに基づいて、適切な型の推論を行います。
- definePropsのバリデーション等の詳細はこちら
defineExpose()
<script setup>
を使用したコンポーネントは、デフォルトで閉じられています。なので、<script setup>
内で宣言されたバインディングを公開しません。
<script setup>
コンポーネントのプロパティを明示的に公開するには、defineExpose コンパイラーマクロを使用します。
defineExpose()を使えば、子供のコンポーネントで宣言されたデータや関数を親コンポーネントに公開できるようになるため、子→親のデータの受け渡しなどに便利です。個人的にはdefineEmits()よりもdefineExpose()を使用することが多いです。
子→親(defineExpose())
<script setup>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => {
count.value++;
};
// defineExposeを使用して、外部からアクセス可能なプロパティとメソッドを定義
defineExpose({
count,
increment
});
</script>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue'
const childComponent = ref()
// valueをつけて呼び出す。
console.log(childComponent.value.count)
const incrementChild = () => {
//子コンポーネントのincrement関数を親コンポーネントから使用する
childComponent.value.increment()
}
</script>
<template>
<!-- defineExposeで公開したプロパティをrefで受け取る -->
<ChildComponent ref="childComponent"/>
<button @click="incrementChild">子コンポーネントのカウントを増やす</button>
</template>
まとめ
この記事では、Vue.jsの<script setup>
構文について詳しく解説しました。<script setup>
構文は、Vue.js 3.2以降で利用可能な、単一ファイルコンポーネント(SFC)内でCompositionAPIをより効率的に使用するためのシンタックスシュガーです。この構文を利用することで、ボイラープレートの削減、より簡潔なコードの記述、ランタイムとIDEのパフォーマンス向上などのメリットがあります。ぜひ使ってみてください!!
参考・引用