4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Vue.js】modelプロパティでコンポーネントを疎結合する

Last updated at Posted at 2021-05-02

はじめに

社内システムのフロントエンドにVue.jsを利用しています。
コンポーネント間でデータのやり取りをpropsやemitでやっていたのですが、
あまりにもそれらの量が多くなりコンポーネントどうしが密結合してしまいました。
状態はVuexで管理するとして、
「ダイアログの開閉動作もなんとか分離できないか?:disappointed_relieved:
ということで、modelプロパティを利用して疎結合にしました。

やること

modelプロパティを使う場合と使わない場合の、ダイアログの開閉動作の実装の違いを見て、
同プロパティが疎結合に寄与していることを確認します。
ダイアログは、VuetifyのVDialogを利用し独自に定義したコンポーネントを使うといった想定です。
※前提知識として、Vue.jsでは親の値を子が直接変更すると、動きはするものの怒られるということを知っておいてください。

環境

今回実行した環境は以下です。

  • Vue.js 2.6.12
  • Vuetify 2.4.3

ソースコード

1. modelプロパティを使わない場合

Parent.vue
<template>
  <div>
   <v-btn depressed @click="openDialog">
      Open
    </v-btn>
   <dialog :value="value" @close="close">
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import dialog from '@/dialog';
export default Vue.extend({
  name: "Parent",
  data: {
    value: false
  },
  methods: {
    openDialog() {
      this.value = true;
    },
    close() {
      this.value = false;
    }
  }
})
</script>
Dialog.vue
<template>
  <div>
    <v-dialog v-model="value">
      <v-card tile>
        hoge
      </v-card>
    </v-dialog>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
  name: "Dialog",
  props: {
    value: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    close() {
      this.$emit("close", false);
    }
  }
})
</script>

2. modelプロパティを使う場合

Parent.vue
<template>
  <div>
   <v-btn depressed @click="openDialog">
      Open
    </v-btn>
   <dialog v-model="value">
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import dialog from '@/dialog';
export default Vue.extend({
  name: "Parent",
  data() {
    value: false
  },
  methods: {
    openDialog() {
      this.value= true;
    }
  }
})
</script>
Dialog.vue
<template>
  <div>
    <v-dialog
      :value="value"
      @input="(val) => val || close()"
    >
      <v-card tile>
        hoge
      </v-card>
    </v-dialog>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
  name: "Dialog",
  model: {
    prop: "value",
    event: "change-dialog"
  },
  props: {
    value: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    close() {
      this.$emit("change-dialog", false);
    }
  }
})
</script>

何が変わったか

1. modelプロパティを使わない場合

やっていること
  1. propsでvalue変数(ダイアログの開閉状態)を渡している
  2. エラーを避けるため、ダイアログを閉じる処理はParent.vueが担っている
良くない点
  1. 処理がParent.vueに依存する部分があるため、コンポーネントが密結合している
  2. 閉じるためにモーダル部分をクリックすると、Dialog.vueがParent.vueのvalue変数を直接書き換えるためエラーとなってしまう
  3. Dialogコンポーネントを使いまわそうとすると、そのたびに親にダイアログを閉じる処理を書かないといけない

2. modelプロパティを使う場合

やっていること
  1. v-modelを経由して、ダイアログの開閉状態をDialog.vueに渡している
  2. v-modelで渡した状態をmodelプロパティ(とprops)で受け取っている
  3. Dialog.vueの@inputイベントでダイアログの開閉状態を監視している
良い点
  1. 処理がDialog.vueで完結しているため、1に比べてコンポーネントが疎結合になっている
  2. モーダル部分をクリックして閉じたとしても、エラーとならない
  3. 親コンポーネントが簡素になる

最後に

modelプロパティを使うことでコンポーネントを疎結合にすることが出来ました。
子コンポーネントにmodelプロパティとprops両方を書かないといけず、記述量が増えるのが難点ですが
疎結合になることで使いまわすことが容易になりました。

4
1
0

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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?