Vuetify でなるべく疎結合になるように ref を使って Modal(モーダル)を実装する

Vuetify は Material デザインベースの Vue 用 Component ライブラリで、豊富なコンポーネントと丁寧なマニュアルが魅力だ。

Vuetify を使って Modal を実装する場合には [v-dialogue component](https://vuetifyjs.com/en/components/dialogs#example-advanced) を使うといいと思う。(他にもっといいのあったら教えてください)

今回はこれをサンプルよりも疎結合になるように実装する方法について。ref を使う。

## 普通に実装する

サンプルを元に簡単に実装すると次のようになる。

https://codesandbox.io/embed/zn9o831624?module=%2Fsrc%2FApp.vue

```vue:App.vue
<template>
  <v-app>
  <div class="text-xs-center">
    <v-dialog
      v-model="dialog"
      width="500"
    >
      <v-btn
        slot="activator"
        color="red lighten-2"
        dark
      >
        Click Me
      </v-btn>

      <v-card>
        <v-card-title
          class="headline grey lighten-2"
          primary-title
        >
          Privacy Policy
        </v-card-title>

        <v-card-text>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
        </v-card-text>

        <v-divider></v-divider>

        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="primary"
            flat
            @click="dialog = false"
          >
            I accept
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </div>
  </v-app>
</template>

<script>
  export default {
    data () {
      return {
        dialog: false
      }
    }
  }
</script>
```

## ただし上記例だと長すぎる。v-dialog 以下を別コンポーネントに切り出す

上記のように普通に実装すると、非常にコードが長くなる。v-dialog 以下を別コンポーネントに切り出したい。`dialog.vue` に切り出すとしよう。

ただしその際に問題になるのは、モーダルの開閉状態を制御する `state` である `dialog` が `App.vue` の中に残ってしまう。`dialog` は `dialog.vue` でしか使わない `state` なので `App.vue` が持っているのはおかしい。これを `dialogu.vue` に任せることにしたい。

しかし、任せたら任せたで難しくなることが一つある。`dialogu.vue` に譲渡した `dialog` を、親コンポーネントである `App.vue` からどのように変更すればいいのか、という問題だ。親コンポーネントから、子コンポーネントの状態を変更したい場合は結構ある。具体的には、親コンポーネントで API からデータを fetch し、それが終わった時に子コンポーネントの modal を開く、といった場合だ。子コンポーネントにアクセスしなくてはいけない。

これを vuex で管理するのも一つの方法だが、この画面でしか使わない state を vuex で管理すべきでない。

今回は ref を使った実装を紹介する。

https://codesandbox.io/embed/n7l1n0q6rm

```vue:dialog.vue
<template>
  <v-app>
    <div class="parent">
      <button class="parent-button" @click="refToParentOpen">parent button</button>
      <app-dialogue ref="childDialogue"></app-dialogue>
    </div>
  </v-app>
</template>

<script>
  import appDialogue from './components/dialogue.vue'
  export default {
    components: {
      appDialogue
    },
    methods: {
      refToParentOpen(){
        this.$refs.childDialogue.open()
      }
    }
  }
</script>
<style scoped>
.parent {
  background: red
}

.parent-button {
  color: white;
  text-align: center;
  background: blue;
}
</style>
```

ポイントは、親コンポーネントで呼び出す「子コンポーネント」に対して、`ref="name"` という形で、参照を紐づけることだ。これによって、親コンポーネントからは `this.$refs.name` で参照することができる。あとは、`this.$refs.name.methodName()` という形で子コンポーネントの持つメソッドにアクセスできる。

注意点として、`:ref="name"` と書いてもうまく機能しないことをあげておく。なぜなら `:ref` は `v-bind` なので `name` という変数を参照することになってしまうからだ。もしこうかくのであれば、前もってこの変数を宣言しておく必要がある。

こうすることで、次のようにフェッチが終わって初めてモーダルを開く実装を親コンポーネントですることができる。

```vue
<script>
    methods: {
      refToParentOpen(){
        this.$refs.childDialogue.open()
      },
      async fetchData(){
        // まずフェッチする
        await axios.get(/api/endpoint)
　　　　 // フェッチが終わって初めてモーダルを開く 
        this.refToParentOpen()
      }
    }
</script>
```