
【Laravel】BladeファイルでVue3のコンポーネントを利用する方法【Vue3】


プログラミングの備忘録と情報発信
Vue.jsでTypeScriptを使う方法、使う際に最低限知っておきたい事をまとめました。データや関数、算出プロパティなど、代表的な機能におけるTypeScriptの記述方法について一通り解説しています。ただし、今回説明する内容はVue.jsのバージョン3以降に登場した「CompositionAPI」を対象にした記述方法となります。そこでVue.js3をあまり知らない方のために、CompositionAPIの使い方にも最低限触れています。
目次
Composition APIとは、Vue.js3から導入されたコンポーネントの新しい仕組みです。
これに対してVue2で一般的に使われていた、
data()、methods :{ }、computed:{ }を並べて利用したものをOptions APIと言います。
Composition APIはOptions APIよりもコードが綺麗に、よりシンプルに書けるようになります。
そして何より、TypeScriptとの親和性が非常に高いです。
本記事でも軽くまとめてありますが、CompositionAPI自体について詳しく知りたい方は是非公式ドキュメントをご参照ください。
Composition API | Vue.js
https://v3.ja.vuejs.org/api/composition-api.html#setup
雛形は以下のようになります。(Vue CLIやViteを使う場合、予め記述されている場合もあります。)
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({
setup() {}
})
</script>
TypeScriptを利用する際のポイントは以下の2点です。
※すぐ後の説明で、この記述方法を簡略化した「script setup構文」の説明をします。
setup関数をご存じの方は読み飛ばして下さい。
CompositionAPIでは、setup関数内にdataや関数、算出プロパティ、watchやライフライクルフック等すべてを書いていきます。これによって、コードが散らばらずに簡潔な記述ができたり、必要な処理だけを集めて外部ファイルに切り出してまとめること(コンポジション関数化)が出来る等、様々な恩恵を受けることが出来ます。
また、setup関数では、<template>側で使いたい変数や関数をreturn文に記述する必要があります。
setup() {
// 変数や関数、computedやwatchの定義はここに書く
const 変数A = ...
const 関数A = () => {...}
return {
templateで使いたい変数A, 変数B, 関数Aなど
}
}
ここまでの説明でピンと来なくても、この記事内にsetup関数の使い方のサンプルを一通り掲載しているので、記事を読み進めれば言わんとしてることが分かるかと思います。
前節で説明したような
…といった煩わしい記述を簡略化できる書き方が導入されました。
scriptタグにsetupを記述し<script lang="ts" setup>
とすると、①~③の記述が一切不要になります。
scriptタグ直下に変数や関数、算出プロパティ等をそのまま記述することが可能になります。
<script lang="ts" setup>
//今までsetup(){}内に記述していたものをこの階層にそのまま記述できます。
//returnもいりません。
//変数
const hoge = ~
//関数
const hoge = () => {}
</script>
本記事のサンプルコードでは通常のsetup関数を使った記述をしていますが、これからVue3で開発するならばscript setup構文のほうが簡単に記述できますし、メリットも多いのでおすすめです。
このscript setup構文について詳しく知りたい方は、次の記事をご覧ください。
従来(Options API)のdata()に相当します。変数の記法は変数の種類によって2つに分かれます。
オブジェクト以外の型はrefでラップします。次のようにインポートする必要があります。
import { ref } from "vue"
普通のJavaScriptで変数を定義する時と同じように、constやletで宣言できるようになりました。
基本的にリテラルはref()
でラップする必要があり、これによってリアクティブになります。
(全く更新する予定のない変数はrefでラップする必要はありません。)
refという関数名の通り、変数は「値」では無く「参照」になります。
また、refをscript内で利用する場合は.valueを付けて利用します。(下記サンプル参照)
template側で利用する際.valueの指定はいりません。
TypeScriptでの書き方ですが、
TypeScriptが自動推論してくれる型(number型やstring型、boolean型等)には特別な記述はいりません。
明示的に型を付ける必要がある場合は、refにジェネリックを使います。(自動推論してくれない場合や、自動推論される型より詳細な型宣言が必要な場合等が該当します。)
export default defineComponent({
setup() {
//型推論
const name = ref('ryo') // Ref<string>型
console.log(name.value)//.valueをつけて参照:string型になる
//明示的な型付け
const age = ref<number | string>(30) // Ref<number | string>型
//明示的な型付け
const element = ref<HTMLElement>() // Ref<HTMLElement | undefined>型
return {
name, age, element
}
}
})
T型の変数をrefでラップした変数の戻り値は、Ref<T>型になります。
例えば、string型の変数ならばRef<string>型になります。
setup関数内でrefの値を参照したい場合は.valueをつけます。
この時、Ref<T>型の変数をT型として参照できます。
オブジェクトにはreactiveを使います。
import { reactive } from "vue"
refと同様に、普通のJavaScriptを使う要領でconst宣言(あるいはlet)します。
オブジェクトはreactive()
でラップすることでリアクティブになります。
refの使い方とあまり変わりませんが、値を参照する際に.valueの指定は必要ありません。
明示的に型を付けたい場合は、初めにオブジェクトの型をtypeやinterfaceで定義します。
その型を変数宣言の際にreactiveと一緒に使うのですが、方法は3つあります。
reactiveでTypeScriptを使う3つの方法
//インターフェースの定義
interface Person {
name: string
age?: number
}
export default defineComponent({
setup() {
//方法1
const person1 = reactive<Person>({
name: 'taro',
age: 22,
})
//方法2
const person2: Person = reactive({
name: 'jiro',
age: 31
})
//方法3
const person3 = reactive({
name: 'hanako',
age: 40
}) as Person
return {
person1, person2, person3
}
}
})
Vue用の特別な記述は要らず、TypeScriptの普通の関数のように書きます。
setup() {
const num = ref(0)
//普通の関数と同じ記述
const add = (n: number) => {
num.value += n
}
return {
num, add
}
}
computed()
を使います。import { computed } from "vue"
用途によって主に2つの書き方ができますが、computedで処理をラップするのは共通です。
算出プロパティの用途による記述の違い
getterのみ使用する場合は、算出プロパティ値をリターンする関数をcomputed()に渡します。
setterも使う場合は、get、setの2つのプロパティを持つオブジェクトをcomputed()に渡します。
それぞれのプロパティの値は関数です。
関数と同様に戻り値は自動的に推論してくれるので、基本的にVue用の特別な記述はいりません。
戻り値の型を自動で推論してくれない場合のみ、computedにジェネリックを使います。
setterを使う場合も使わない場合もcomputedの中には関数を使用していますが、この関数はTypeScriptで書きます。
setup() {
const num = ref(0)
//算出プロパティ
const computedNum = computed(() => num.value + 5)
//戻り値の型を推論してくれない場合
const computedString = computed<string>(() => //何らかの処理)
// getterとsetter
const computedNum2 = computed({
get: () => num.value + 30,
set: (val: number) => num.value += val
})
return {
computedNum, computedNum2
}
}
watch()
を使います。import { watch } from "vue"
watch()
の第一引数に監視対象、第二引数に処理を関数で渡します。
TypeScript用の特別な記述はいらず、この第二引数の関数をTypeScriptで書きます。
setup() {
const num = ref(0)
//ウォッチ
watch(num, (oldValue, newValue) => {
/* 処理 */
})
}
watchには類似関数として、watchEffect、watchPostEffect、watchSyncEffectがあります。
watch及びこれらwatch系の関数の詳しい使い方は次の記事をご参照下さい。
onXXXX()
の形で処理の関数をラップします。この関数をTypeScriptで書きます。これもインポートが必要です。
setup() {
onMounted(() => console.log('mounted'))
onBeforeUnmount(() => console.log('before unmount'))
}
PropTypes<T>
を使って具体的に型を指定できます。import { PropTypes } from "vue"
もともとのpropsでもざっくりとした型を定義できましたが、PropType<T>でアサーションすることによって、より詳細に型を宣言できます。
export default defineComponent({
props: {
msg: String, // プリミティブ
txt: String as PropType< 'hoge' | 'fuga' >, // リテラル型の合併
person: Object as PropType<Person>, // Person型
ary: Array as PropType<number[]>, //number[]型
func: Function as PropType<(n : number) => void> // 関数型
},
setup() {}
})
きちんと型を指定すれば、validatorのチェック項目が減らせる場合がある(リテラル型の合併など)
propsについてもう少し踏み込んで説明します。
setup()内でpropsを扱う方法を説明します。
そもそも、setup()の第一引数はpropsを取ります。
setup(props) {}
このpropsはリアクティブです。
リアクティブを維持したままpropsをsetup()内で利用する時、いくつか注意することがあります。
toRef()でラップすることによってリアクティブを維持したままprops内のデータを扱えるようになります。
const prop = toRef(props, '使用したいprops名')
propsが複数ある時、toRefs()でpropsをラップするとリアクティブを維持したまま分割代入が可能です。
const { props1, props2 } = toRefs(props)
toRefやtoRefsでpropsを参照できますが、propsの値をコンポーネント内で変更したい場合は、propsのコピーを変更するようにします。
//変更するならばRefではなくコピーを参照
const hoge = ref(props.プロパティA)
以下のようにdefinePropsを使います。インクルード不要です。
<script lang="ts" setup>
const props = defineProps({
//この中は従来のpropsの記述と同じ
props1: {
type: String,
required: true
}
})
</script>
ジェネリックを使えば更に簡略化して記述できます。
詳しくは次の記事をご覧ください。
emitを使う場合は、setup()
の第二引数ctxプロパティの中にあるemitを使います。ctx.emit()
のようにして利用します。
また、emitするイベント名をemits:["emitするイベント名"]
として宣言します。この宣言はsetup()の中ではなく、外にsetup()と併記します。
emits: ['add'],
setup(props, ctx) {
const add = () => ctx.emit('add', 5)
}
emitsの具体的な使用例として、カスタムコンポーネントのv-modelにおけるemitの使い方を別記事でまとめています。
defineEmitsを使います。
<script lang="ts" setup>
const emit = defineEmits(['emit1', 'emit2'])
</script>
少々長くなるので別の記事でまとめています。
ref()
を使います。参照したいHTML要素にテンプレートでref属性をつけ、setup()内でrefを使ってアクセスしてreturnで返します。
<template>
<p ref="msg">Hello</p>
</template>
setup() {
const msg = ref<HTMLParagraphElement>()
onMounted(() => console.log(msg.value?.textContent))
return {
msg
}
}
プリミティブ型で使ったref()と同じものです
v-forで生成したDOMをテンプレート参照する方法は、次の記事を参照下さい。
Vuexでもある程度TypeScriptを使用できます。
store.tsで使うステートの型の定義をします。
createStore()
を使う際に、ステートの型をジェネリックで渡します。
export const store = createStore<State>({})
ゲッターやミューテーション、アクションは特別な記述の必要はなく、普通のTypeScriptの関数のように書けます
nameとageの2つのステートのみを持つ簡単な例を挙げます。
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import { InjectionKey } from 'vue'
// ステートの型を定義
export interface State {
name: string
age: number
}
// InjectionKeyの発行
export const key: InjectionKey<Store<State>> = Symbol()
// createStoreにStateをジェネリックで渡す
export const store = createStore<State>({
state: {
name: 'taro',
age: 10
},
mutations: {
setName(state, payload: string) {
state.name = payload
},
setAge(state, payload: number) {
state.age = payload
},
},
actions: {
changeName({ commit, state }, payload: string) {
commit('setName', payload)
},
changeAngle({ commit, state }, payload: number ) {
commit('setAge', payload)
}
}
})
// 外部で利用
export const useStore = () => baseUseStore(key)
インジェクションキー周りの記述はやや複雑ですが決り文句です。
storeを使う側は
import { useStore } from './store'
export default defineComponent({
setup() {
const store = useStore()
console.log(store.state.age)
}
})
app.mount(#app)
の前にキーの登録 app.use(store, key)
を忘れずに記述しましょう。
ルーティング情報が入っている配列にジェネリックで型付けします。Vue CLIでプロジェクトを作成した場合は元から記述されています。
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "HOME",
component: HOME,
},
{
path: "/about",
name: "About",
component: () =>
import(
"../views/About.vue"
),
},
]
この配列をcreateRouter()に渡します。
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
ナビゲーションガードについて知りたい方はこちらの記事をご覧ください。
Composition APIの基本的な機能とTypeScriptの使い方をざっと見てきました。
箇条書き形式で紹介したので、目次から調べたい機能にジャンプできます。
また、広く浅くさらっただけなので紹介し切れていない部分も多いですが、別の機会に解説したいと思います。