LoginSignup
24
9

【vue.js】<script setup>の基本

Last updated at Posted at 2023-12-11

はじめに

※この記事は「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>ブロックに追加します。

app.vue
<script setup>
    console.log('Hello vue')
</script>

<script setup>構文はCompositionAPIにおけるsetup()関数内部を<script>直下に直接記述することができるという構文です。通常の<script>とは違って、コンポーネントが最初にインポートされたときに一度だけ実行されるのではなく、<script setup>内のコードはコンポーネントのインスタンスが作成されるたびに実行されます。

変数、関数宣言、インポート

<script setup> を使用する場合、<script setup>内で宣言された変数、関数宣言、インポート等はテンプレートで直接使用できます。

  • Option API
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
<script setup>
// 変数
const msg = 'Hello!'

// 関数
const log = () => {
    console.log(msg)
}
</script>

<template>
    <button @click="log">{{ msg }}</button>
</template>

インポートも同様に、インポートされたヘルパー関数をmethodsオプションで公開することなくテンプレート内の式で直接使用できます。

  • Option API
Option API
<script>
import { capitalize } from './helpers';

export default {
    methods: {
        capitalize
    }
}
</script>

<template>
    <div>{{ capitalize('hello') }}</div>
</template>
  • <script setup>
script setup
<script setup>
import { capitalize } from './helpers'
</script>

<template>
    <div>{{ capitalize('hello') }}</div>
</template>

リアクティビティー 

リアクティブな状態はリアクティビティーAPIを使って明示的に作成する必要があります。setup()関数から返された値と同じように、テンプレート内で参照されるときに自動的にアンラップされます。

  • Option API
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
<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
<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
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
<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
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
<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
Option API
<script>
import MyComponent from './MyComponent.vue';

export default {
    components: {
        MyComponent
    }
};
</script>

<template>
    <MyComponent />
</template>
  • <script setup>
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>
子コンポーネント(ChildComponent.vue)
<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>
子コンポーネント(ChildComponent.vue)
<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>
子コンポーネント(ChildComponent.vue)
<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のパフォーマンス向上などのメリットがあります。ぜひ使ってみてください!!

参考・引用

24
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
9