カクカクしかじか

技術的なアレコレ

Vue.js 2系(Nuxt.js 2系)でモーダルの開閉フラグをv-modelで扱うときの注意点とVue.js 3系での変更点

概要

Nuxt.jsの2系でモーダルを実装する際にモーダルの開閉フラグをv-modelで管理する際のイベント発火させる動作がややこしかったので今回はそれをまとめます。

結論

inputイベントでなくても v-model を使う場合は、
コンポーネントで指定した v-model="hoge" の値を子コンポーネントでは propsのvalueで受け取って、
emit('input', 送りたい値) で親に送り返す必要がある。

実装例

/shared/Modal/index.vue

コンポーネント(モーダルコンポーネント)では propsにvalue という値を定義して、inputイベントで送りたい値をemitする

<template>
  <div v-if="value" class="modal" v-bind="$attrs" @click.stop="close">
    <section class="modal-window" @click.stop>
      <button v-if="!hiddenCloseButton" class="nn-icon-button close-button" @click="close">
        <IconClose class="nn-icon" />
      </button>
      <header v-if="title" class="title-container">
        <h1 class="title">{{ title }}</h1>
      </header>
      <div>
        <slot name="body" />
      </div>
      <footer>
        <slot name="footer" />
      </footer>
    </section>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import IconClose from "@/assets/icons/close.svg";

export default Vue.extend({
  name: "Modal",
  components: {
    IconClose,
  },
  props: {
    value: { // vue.js 2系の場合は、「value」でpropsの値を定義する必要がある
      type: Boolean,
      required: true,
    },
    hiddenCloseButton: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: "",
    },
  },
  methods: {
    close() {
      this.$emit("input", false);
    },
  },
});
</script>

/pages/plan/index.vue

v-model="modalVisible" でモーダルの開閉フラグを渡す

<Modal v-model="modalVisible" :title="'プレミアムプランの解約'">
  <template #body>
    <p class="description">
       この操作は取り消すことができません
    </p>
    <CancellationFormContainer />
  </template>
  <template #footer>
    <div class="action-container">
      <div class="button-container">
        <button class="primary-button type-negative type-small" @click="switchModalDisplay">
          キャンセル
        </button>
        <button class="primary-button type-primary type-small" type="submit">
          解約する
        </button>
      </div>
    </div>
  </template>
</Modal>

<script lang="ts">
import Vue from "vue";
import Modal from "@/components/shared/Modal/index.vue";
<中略>
  data() {
    return {
      modalVisible: false,
    };
  },
  <中略>
  methods: {
    switchModalDisplay(): void {
      this.modalVisible = !this.modalVisible;
    },
  },
});
</script>

Vue.js 3系での変更点

v3.ja.vuejs.org

引数のないすべての v-model について、プロパティとイベントの名前をそれぞれ modelValue と update:modelValue に置き換えてください。

<ChildComponent v-model="pageTitle" />
// ChildComponent.vue

export default {
  props: {
    modelValue: String // 以前は `value:String` でした
  },
  emits: ['update:modelValue'],
  methods: {
    changePageTitle(title) {
      this.$emit('update:modelValue', title) // 以前は `this.$emit('input', title)` でした
    }
  }
}