vue と react の DOM の再レンダリングの実行タイミングの差


はじめに

私自身、vue は 2019 年頃から書いており、今年で 3 年目くらいです。たいして vue に詳しくもないですが、それなりのコード量を書いてきたので、フレームワークとして愛着はあります。今年から react を触る機会が増えそう、ということで、ちょっとづつ react の勉強をはじめていたのですが、vue との明確な違いとして「DOM の再レンダリングの実行タイミングの差」が気になっています。react はドキュメントを読んでいると、「不要な再レンダリングを防ぐ」というトピックを扱うドキュメントが多いように思いますが、vue を勉強しているときは、正直、あまり意識をしていなかった領域です。なぜ、vue を書いているときは意識していなかったのか、どういう差があるのか、などちょっと気になったベースで調べて、記事にしています。

注意点

ソースは、公式ドキュメントを確認するようにしていますが、私自身が公式ドキュメントを誤読することはありますので、この記事で間違ったことを書く可能性はとても高いです。間違った内容の記載があればコメントで教えてください。

vue 公式からの言及

当然ながらこのトピックに関しては、vue の公式から言及されています(react より後発のフレームワークであるためか、その外にも react との違いについて言及されています)。

以下、react についての抜粋。

React では、コンポーネントの状態が変化するとき、そのコンポーネントをルートとして、コンポーネントのサブツリー全体を再描画
https://jp.vuejs.org/v2/guide/comparison.html#%E5%AE%9F%E8%A1%8C%E6%99%82%E6%80%A7%E8%83%BD

以下、vue についての抜粋。

Vue では、コンポーネントの依存関係が描画中に自動的に追跡
https://jp.vuejs.org/v2/guide/comparison.html#%E5%AE%9F%E8%A1%8C%E6%99%82%E6%80%A7%E8%83%BD

※ このドキュメントは、vue2 の時のもので、vue2 と vue3 の依存関係の追跡の仕組みはガラッと変わってはいますが、根本的な部分は変わっていないので、このドキュメントに記載されている粒度では、以前、vue と react の「DOM の再レンダリングの実行タイミングの差」として扱えると思っています。

引用の公式ドキュメントを読んで、このトピックは終わりという感じではありますが、自分の理解として落とし込めるように上記のことについて少しコードベースで追っていきます。

react の再レンダリングのタイミングを確かめる

react はコンポーネントの状態 (props や state) が変更された場合に、変更されたコンポーネントとその子コンポーネントも含めて、再レンダリングが実行されます。

以下のコンポーネントの場合、props や state に変更があった場合に Clock というコンポーネントは再レンダリングが実行されます。また、Clock コンポーネントが子コンポーネントを持っていれば、子コンポーネントも再レンダリング対象となります。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

vue の再レンダリングのタイミングを確かめる

一方、vue の再レンダリングは監視対象となっている変数が変更された場合に、その変数を使用しているコンポーネントのレンダリングが実行されます。変数を監視対象にするには、vue から提供されている関数でラップします。

以下のコンポーネントの場合、監視対象にしたい変数を ref 関数等でラップします。そして、監視対象となった readersNumber の値が変更された場合に再レンダリングされます。

<template>
  <div>{{ readersNumber }}</div>
</template>

<script>
  import { ref } from 'vue'

  export default {
    setup() {
      const readersNumber = ref(0)

      return {
        readersNumber
      }
    }
  }
</script>

vue と react の DOM の再レンダリングの実行タイミングの差

react は、コンポーネントの状態が変更されたタイミングで、変更されたコンポーネントとその子コンポーネントも含めて再レンダリングが実行されるのに対して、vue は、監視対象の変数が変更されたタイミングで、その変数に依存しているコンポーネントが再レンダリングされます。JavaScript のフレームワークとしてよく比較される両者ですが、再レンダリングの実行タイミングに関しては、明確に違うな、という印象です。

vue を書いているとき、なぜ再レンダリングを意識していなかったのか

vue は、監視対象の変数の値が変更された場合に再レンダリングが実行されるため、値の変更と再レンダリングの実行がイコールとなり、より直観的です。開発者目線だとわかりやすいとも言えます。vue を書いているときに再レンダリングを意識していなかった理由としては、不要な再レンダリングが起きにくいため、意識する必要もあまりないためだと思います。

比べて、react は変更されたコンポーネントとその子コンポーネントも含めて再レンダリングが実行されるため、意図しないコンポーネントまで再レンダリング対象となる場合があります。また、再レンダリングされるコンポーネントは開発者目線だとすぐには判別できないため、開発者目線だと再レンダリングのタイミングはわかりにくい、と言えると思います(props や state の変更や親コンポーネントの再レンダリング有無などをコードベースで追跡していく必要があります)。

では、vue の方が優れているのか、と言われると、もちろんそんなことはなく、再レンダリングの対象がわかりやすいだけで、それがアプリケーションの品質に良い影響を与えるかと言われると、状況によって様々です。vue の再レンダリングの仕組みがアプリケーションに与える悪い方の影響を次の章で記載してみます。

vue の再レンダリングの仕組みによって埋め込まれやすいバグ

vue を書いていて、監視対象の値を変更しても再レンダリングが実行されない、ということがあります。これは監視対象の変数が気づかないうちに監視対象外となっているため起こるのですが、こういったバグは vue の再レンダリングの仕組み上、けっこう埋め込まれやすいです。例えば、監視対象の変数を props で子コンポーネントに渡すと、監視対象外となります。こういったバグを防ぐために ESLint で検知できるようにプラグインを入れたりしますが、vue を書いていて、変数が監視対象なのかどうか、というのは常に気にするところです。まとめると不要な再レンダリングの実行が少ない分、必要なレンダリングが実行されないことがある、という感じです。

react では props と state が更新された場合には子コンポーネントも含めて、再レンダリングが実行されるため、上記のようなバグは埋め込まれにくいです。その分、不要な再レンダリングの実行によりパフォーマンスの低下が懸念されるわけですが...

react で不要な再レンダリングを防ぐ仕組みとして、shouldComponentUpdateReact.PureComponent がありますが、これらを多用すれば上記に記載した vue のように必要な再レンダリングが起きない、というバグが発生しやすくなる、というのは言わずもがなだと思います。

まとめ

どっちの再レンダリングの実行タイミングが最適かは、状況によって変わるため、開発者としては「違い」として認識しておくことが大事ですかね。以上、終わり。