Help us understand the problem. What is going on with this article?

React.jsのVirtualDOMについて

More than 3 years have passed since last update.

今回はReact.jsのVirtualDOMの実装での工夫について書きたいと思います。

Version control for the DOM

React.jsのVirtualDOMの実態はJavaScriptのオブジェクトであり、rerenderする際に前後の状態を比較して最小限の変更だけを実際のDOMに反映させる仕組みになっています。

つまり、バージョン管理されていてdiffだけをpatchとして実際のDOMに適用する感じですね。

Level by level

単純にVirtualDOMのtreeを比較すると計算量が多くなってしまうので、React.jsでは計算量を減らすための工夫がされています。

その1つがVirtualDOM treeの同階層同士でしか比較しないということです。WebアプリケーションのDOM構造で異なる階層に要素が移動するケースは珍しいという理由でこのようになっていて、これによってアルゴリズムの複雑性が削減されています。

<div><Header title="foo" /></div><div><Header title="bar" /></div>

はよくあるけど

<div><Header title="foo" /></div><div><div><Header title="foo" /></div></div>

みたいなことはあまりないので、この場合は、Header Componentは破棄されて再度作成される。

List

例えばあるリストの真ん中に要素を追加した時に、それぞれの要素を適切にマッピングして真ん中に要素が追加されたことだけを反映させるのはなかなか難しいです。

React.jsではkey属性を与えることで要素のマッピングを教えることが出来ます。

<ul>
  {this.props.list.map(element => <li key={element.id}>{element.body</li>)}
</ul>

keyはそのデータのIdentifyを指定するべきです。indexとかにしても...

[
  { id: 1, name: "foo" },
  { id: 3, name: "bar" },
  { id: 5, name: "baz" }
]

[
  { id: 1, name: "foo" },
  { id: 2, name: "foofoo" },
  { id: 3, name: "bar" },
  { id: 5, name: "baz" }
]

のようなデータ構造があるときは、idをkeyとして指定しておくといいです。

Components

React.jsは同じComponent同士に対してしかdiffアルゴリズムを適用しないので、すべてをdiv要素にするのではなくて独自のComponentを定義して使うことで無駄なdiffの計算を減らすことが出来ます。

<div>header1</div> -> <div>header2</div>だと中の要素を比較しますが、<Header1 /> -> <Header2 />だとHeader1を削除してHeader2を追加となります。逆にいうとHeader1とHeader2の違いが些細な場合は、同じComponentにして差分だけを適用した方がいいということになります。

Rendering

Batching

React.jsではsetStateするとそのComponentはdirtyであるとチェックされて、イベントループ毎にdirtyなComponentをrerenderすることで実際のDOMへの反映自体を最低限の回数にしています。

この工夫はVue.jsなど他のフレームワークやライブラリでも見られます。

Subtree rendering

また、setStateされたときは子のComponentもrerenderされます。なのでTopレベルのComponentでsetStateすると全てのComponentがrerenderされます。でも実際のDOMへは変更された箇所しか適用されないのでパフォーマンス的にはそんなに問題にならないです。VirtualDOMのdiffの計算がありますが実際のDOM操作に比べるとコストが低くVirtualDOMのなせる技です。

それによって、Topレベルの要素にデータをまとめてもたせておいてそれをsetStateで更新して都度アプリケーション全体をrerenderする構造が可能になってアプリケーションの構造を単純化することが出来るメリットもあります。

Selective sub-tree rendering

さらにパフォーマンスが求められるような場合には、boolean shouldComponentUpdate(object nextProps, object nextState)を実装することでパフォーマンスを向上させることが出来ます。ここでfalseを返すとそのComponentとそれより下位のComponentがrerenderされなくなります。

ここでの比較を単純にするために、StateやPropをimmutableなdata構造にするというアプローチもあります。

immutable data

その辺りのソリューションとして、immutableなdataを持つClojureScriptでのReact.js実装であるOmや、Facebookが作っているimmutableなデータ操作を可能にするimmutable.jsがあったりします。

またReact.jsにもReact.addons.updateというAddonがあるのでそれを使うことも可能です。

immutableなデータ構造にすることによってundoなんかの実装を作りやすくなったりします。


最後少し話がそれましたが、そんな感じでDOM操作を最低限にしたりdiffの計算量をO(n^3)からO(n)にするためにReact.jsが工夫しているという話でした。

参考

http://calendar.perfplanet.com/2013/diff/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした