tomatoaiu の Tech Blog

プログラミングやツールについてのまとめブログ

Vue.js 子コンポーネントを強制的に再描画するいくつかの方法

motivation

子コンポーネントを再描画したい。いくつかのやり方を見つけたのでメモ。

公式の注意書きでは、以下のように書いてあります。

もし Vue で強制更新をする必要な場面に遭遇する場合、99.99% のケースであなたは何かを間違えています。

しかし私の場合は、特定のライブラリが更新されないという特異的な状況に出会ったので、子コンポーネントを強制的に再描画させる方法を模索しました。

サンプルリポジトリです。今回、紹介している各方法のコードを載せています。
github.com

prerequisites

環境についてです。

versions

  • vue: 2.6.10
  • core-js: 2.6.5

components

再描画したい子コンポーネントです。

▼ Child.vue

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+ 1</button>
  </div>
</template>

<script>
export default {
  name: "Child",
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>

solutions

3つの方法を見つけましたので、順に紹介。

v-if

v-if を利用すると、再計算されるようです。単純に、true と false の切り替えだと、再計算されなくなるかもなので、nextTick を利用して、DOMの更新サイクル後に、子コンポーネントを再計算させます。

<template>
  <div>
    <button @click="toggle">rerender by v-if</button>
    <child v-if="showChild"></child>
  </div>
</template>

<script>
import Child from "./Child.vue";

export default {
  name: "Vif",
  components: {
    Child
  },
  data() {
    return {
      showChild: true
    };
  },
  methods: {
    toggle() {
      this.showChild = false;
      this.$nextTick(() => (this.showChild = true));
    }
  }
};
</script>

key

v-for を利用したときによく使う key ですが、普通のコンポーネントにも利用できるみたいです。key を更新することで、前のkeyのDOMが破棄/削除され、同じ場所に新しいkeyの子コンポーネントが生まれる?

<template>
  <div>
    <button @click="toggle">rerender by key</button>
    <child :key="key"></child>
  </div>
</template>

<script>
import Child from "./Child.vue";

export default {
  name: "Key",
  components: {
    Child
  },
  data() {
    return {
      key: 0
    };
  },
  methods: {
    toggle() {
      this.key = this.key ? 0 : 1;
    }
  }
};
</script>

instance

最後は、JavaScriptでごりごり書く方法。最初にtextContent = nullを使うことで、前の子コンポーネントを削除。そのあとに、子コンポーネントのインスタンスを生成させて、appendChild()でDOMに追加という感じ。
見た目で他の再描画の方法より再描画している感が出ているので、分かりやすさはある。

<template>
  <div>
    <button @click="toggle">rerender by instance</button>
    <div ref="parent">
      <child></child>
    </div>
  </div>
</template>

<script>
import Vue from "vue";
import Child from "./Child.vue";

export default {
  name: "Instance",
  components: {
    Child
  },
  methods: {
    toggle() {
      this.$refs["parent"].textContent = null;
      const ComponentClass = Vue.extend(Child);
      const instance = new ComponentClass();
      instance.$mount();
      this.$refs["parent"].appendChild(instance.$el);
    }
  }
};
</script>

この場合で、子コンポーネントにemitがあり、それを親で購読したい場合は以下を参照。 tomatoaiu.hatenablog.com

conclusion

子コンポーネントを再描画したいときに利用できる3つの方法(v-if, key, instance)を書きました。どうしても再描画が必要な特殊な処理を書く場合には、いずれかの方法を利用することで問題を解決できそうです。他にも、forceUpdateを呼ぶ方法が利用できそうですが要調査。

大抵の場合には、これらの方法は利用しないと思いますが、頭の片隅に置いておくといつか使えるかもしれません。

私の場合は、instanceを生成する方法で再描画されない問題を対処しました。きっともう使うことはないでしょう……。

references

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

  • 作者:MIO
  • 発売日: 2018/05/29
  • メディア: Kindle版