LoginSignup
1
1

More than 3 years have passed since last update.

VuetifyでnextTickしても、良いタイミングで処理が動かないときの対応(transition)

Last updated at Posted at 2020-11-05

nexttickの使い方

Vuejsでは、ある値を変更し、nexttickをすることで、リアクティブに動く値が変更したあとの処理を実行することができます。

sample.vue
<template>
 <div id='name'>{{name}}</div>
</template>
<script>
methods: {
  setNameAndSayHello(name) {
    this.name = name
    console.log(document.getElementById('name').innerHTML)
  }
}
</script>

setNameAndSayHello()の中で参照されているDOMはまだ描画されていないです。そのためまだ古い値が使われています。

sample.vue
<script>
methods: {
  setNameAndSayHello(name) {
    this.name = name
    this.$nextTick(function () {
        console.log(document.getElementById('name').innerHTML)
    })
  }
}
</script>

nextTickの中で実施することで、描画されたあとの値を利用することができます。

VuetifyのVDialogでダイアログ閉じたあとの処理の実装をnextTickでやった場合

印刷確認ダイアログを実装したく、以下のようにv-dialogを使い実装しました。

PrintDialog.vue
<template>
  <v-dialog v-model="dialog" width="500">
      <template v-slot:activator="{ on, attrs }">
        <v-btn  v-bind="attrs" v-on="on">印刷</v-btn>
      </template>
      <div class="confirm">
      印刷してよろしいですか?
        <div>
          <v-btn  @click="print">はい</v-btn>
          <v-btn  @click="dialog=false">いいえ</v-btn>
        </div>
      </div>
    </v-dialog>
</template>
<script>
methods: {
  print(e) {
    this.dialog=false
    this.$nextTick(() => {
      window.print()
    })
  }
}
</script>

しかし、ダイアログが完全に閉じない段階で印刷が動いてしまい、ダイアログも印刷されてしまいます。nextTickが効かないと感じてしまいます。

これは、ダイアログを閉じる処理がCSSのアニメーションにより、だんだん消えるようになっているため、ダイアログが残ったままになってしまうためです。
これのかんたんな対応策としてはsetTimeout()を使うことです。

PrintDialog.vue
<script>
methods: {
  print(e) {
    this.dialog=false
    setTimeout(()=> {
      window.print()
    }, 500)
  }
}
</script>

これでダイアログが閉じたときぐらいに、print()処理が呼び出されて、無事にダイアログを除いて印刷されます。
でもこれではちゃんとDOMがなくなったをしっかり捕捉できているかわからないです。

VuetifyのCSSアニメーションの実装

VuetifyのVDialogは、VueのTransitionにより実装されています。

VueのTransitionだと、ダイアログが閉じた場合をハンドリングするには、afterLeaveを指定すれば良いのですが、それが実装されていないです。。。

そこで以下のようなクラスを作ります。

TransitionableDialog.vue
<script>
import Vue from "vue";
const VDialog = Vue.options.components["VDialog"];
export default {
  name: "transitonable-dialog",
  extends: VDialog,
  methods: {
    afterLeave(e) {
      console.log("in afterleave");
      this.$emit("afterLeave", e);
    },
    genTransition() { // ここは、親クラスと同じ内容をコピー
      const content = this.genInnerContent();
      if (!this.transition) return content;
      return this.$createElement(
        "transition",
        {
          props: {
            name: this.transition,
            origin: this.origin,
            appear: true,
          },
          on:{ // afterLeaveイベントを追加
              afterLeave:this.afterLeave
          }
        },
        [content]
      );
    },
  }
};
</script>

extendsの記載の詳細はVuetifyのFAQを参照。
(古いVuetifyだと、genTransitionが上書きできずrender自体を上書きするなどが必要になります。。。)

これにより、afterLeaveイベントを捕捉できるようになりました。

ConfirmDialog.vue
<template>
    <TransitionableDialog v-model="dialog" width="500"
      @afterLeave="print">
      <template v-slot:activator="{ on, attrs }">
        <v-btn  v-bind="attrs" v-on="on">
          印刷
        </v-btn>
      </template>
      <div class="confirm">
      印刷してよろしいですか?
      <div>
        <v-btn @click="dialog=false;closeWithOk=true">
          はい
        </v-btn>
        <v-btn @click="dialog=false">
          いいえ
        </v-btn>
        </div>
      </div>
    </TransitionableDialog>
</template>
<script>
  data(){
    return {
      dialog:false,
      closeWithOk:false
    }
  },
  watch: {
    dialog(v) {
      if(v) {
        this.closeWithOk = false
      }
    }
  },
  methods: {
    print(e) {
      if(!this.closeWithOk) {return}
      window.print()
    }
  }
</script>

Dialog以外のコンポーネント

Vuetifyでは、Dialog以外にVTabItemも同じように「タブの表示が完了してから」を捕捉するために、拡張して上書きしてなどできると思います。
VTabItemの場合には、親クラスのVWindowItemですでにonAfterTransitionがいるのでこいつを上書きし、最後にemitしてあげれば良いと思います。

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