Laravel9/ViteでVue3

Vue2ではdataやcomputed、mountedなどoptionsごとにロジックをまとめて記述するOptions APIだったが、Vue3でリリースされたComposition APIではロジックを中心に自由に関数定義できる。Composition APIがこれからの主流になりそうなので切り替えていきましょう。Laravelから利用する方法とVue3でのコンポーネントの作成方法などをまとめておきます。

開発環境

Windows10
Docker Desktop
WSL2
Laravel9
Laravel Sail

Vue3のインストール&設定

Vue3のインストール

$ sail npm install vue@next vue-loader@next @vitejs/plugin-vue

Viteの設定

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue'	// 追加

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css',
            'resources/js/app.js',
        ]),
        vue(),		// 追加
    ],
    server: {
        hmr: {
            host: 'localhost',
        },
    },
});

Laravelの設定

Route::get('/app/{any?}', function() {
    return view('app');
});
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">

    <!-- Scripts -->
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
  <div id="app"></div>
</body>
</html>

インスタンスの生成

import './bootstrap';
import { createApp } from 'vue'

import App from './components/App.vue'

const app = createApp(App)
app.mount("#app")

基本レイアウトはApp.vueで行って、VueRouterで画面切り替えればいいね。

コンポーネント呼び出し

app.jsでコンポーネントをインポート

import './bootstrap';
import { createApp } from 'vue'

import App from './components/App.vue'
import TestHeader from './components/UserHeader.vue'
import TestFooter from './components/UserFooter.vue'

const app = createApp(App)
app.component('user-header', UserHeader)
app.component('user-footer', UserFooter)
app.mount("#app")
<template>
  <user-header />
  <router-view />
  <user-footer />
</template>

app.vueでコンポーネントをインポート

<template>
  <user-header />
  <router-view />
  <user-footer />
</template>

<script>
import UserHeader from './components/UserHeader.vue'
import UserFooter from './components/UserFooter.vue'
<script>

この場合はパスカルケースとケバブケースのどちらでも呼び出すことができるのね。

<UserHeader />
<user-header />

また、is属性で呼び出すこともでき、動的にコンポーネントを切り替えることもできるみたいね。

<component :is="UserHeader"/>

Vueコンポーネント作成

従来の「Options API」でも、Vue3から導入された「Composition API」でも、さらにVue3.2から導入された「構文」でも作成することができる。書き方の違いを確認しておきましょう。

ライフサイクルフックについは名称が変わってるので要確認ね。
https://v3.ja.vuejs.org/guide/composition-api-lifecycle-hooks.html

Options APIで作成

<template>
  <h1>Counter</h1>
  <p>Count: {{ count }}</p>
  <p>doubleCount: {{ doubleCount }}</p>
  <button @click="increment">++</button>
</template>

<script>
  export default {
    data() {
      return {
        count: 0,
      }
    },
    computed: {
      doubleCount: function() {
        return this.count * 2
      },
    },
    mounted() {
      console.log("mounted.")
    },
    watch: {
      doubleCount(val, old) {
        console.log(old)
      }
    },
    methods: {
      increment: function() {
        this.count++
      },
    },
  }
</script>

Composition APIで作成

<template>
  <h1>Counter</h1>
  <p>Count: {{ count }}</p>
  <p>doubleCount: {{ doubleCount }}</p>
  <button @click="increment">++</button>
</template>

<script>
  import { defineComponent, ref, computed, onMounted, watch } from 'vue'
  export default defineComponent({
    setup() {
      const count = ref(0)
      const doubleCount = computed(() => {
        return count.value * 2
      })
      onMounted(() => {
        console.log("mounted.")
      })
      watch(doubleCount, (val, old) => {
        console.log(old)
      })
      const increment = () => {
        count.value++
      }
      return {
        count,
        doubleCount,
        increment,
      }
    }
  })
</script>

使用する関数はimportしないといけないのね。
ref関数については後ほど…

<script setup>構文で作成

<template>
  <h1>Counter</h1>
  <p>Count: {{ count }}</p>
  <p>doubleCount: {{ doubleCount }}</p>
  <button @click="increment">++</button>
</template>

<script setup>
  import { ref, computed, onMounted, watch } from 'vue'
  const count = ref(0)
  const doubleCount = computed(() => {
    return count.value * 2
  })
  onMounted(() => {
    console.log("mounted.")
  })
  watch(doubleCount, (val, old) => {
    console.log(old)
  })
  const increment = () => {
    count.value++
  }
</script>

・export default defineComponent({ setup() {} })を省略できる!
・returnもしなくていい!
と簡単!
書き方がいろいろあると困るんだけど、簡単なほうがいいのでこれからは<script setup>構文で!

refとreactiveについて

Composition APIではリアクティブな変数は明示的に定義しなければならないのね。

<template>
  <h1>Ref/Reactive</h1>
  <p>Count: {{ count }}</p>
  <p>UserCount: {{ user.count }}</p>
  <button @click="increment">++</button>
</template>

<script setup>
  import { ref, reactive } from 'vue'
  const count = ref(0)
  const user = reactive({
    name: 'taro',
    count: 0,
  })
  const increment = () => {
    count.value++
    user.count++
  }
</script>

ref関数

・プリミティブ(オブジェクト以外)を対象にリアクティブな変数として定義。
・唯一のプロパティ「value」を持ち、値の取得・操作にはこのvalueプロパティを操作。
・<template>内ではアンラップされvalueが表示される。

reactive関数

・オフジェクトを引数にリアクティブにしたコピーを返す。
・reactiveで定義した値については各プロパティを直接操作できる。

その他

<template>が先かが先か問題

Vue3から公式の順番が<template>→<script>になっているが、プロジェクト全体で統一されていれば逆でも良いらしい。
[参考]単一ファイルコンポーネントのトップレベルの属性の順序