読者です 読者をやめる 読者になる 読者になる

React勉強会1(2016-05-25)

発端

やりたいと言ったら @mizchi がやってくれることになった。

今回の目標

weapons.json · GitHub

メインクエスト : この json を使って、Splatoon のブキを一覧表示する機能を作る
サブクエスト : 絞り込み機能を付ける
サブクエスト : 検索フォームと一覧部分をサブコンポーネントに分けて、それらを管理するルートコンポーネントstate の更新内容を書き戻す

進捗

割と実践的で、構築しながら説明と言う感じだった。実際 GitHub - dolpen/react-tutorial のコミットログを見た方が何をしたかは分かりやすいのではないかと思うので大胆に割愛。

コンポーネントは何を与えられるべきか

React 自体は data => view を担当するのが主機能なので、view を出力するコンポーネントの構築に、この data に当たるもの以外が依存として入ってはいけないと感じた。具体的には stateprops で、render() に必要な情報が全て賄われるべきだと思った。

種類 何が入るべきか
state そのコンポーネントが管理する状態
props コンポーネントから渡される情報

仮想DOMと再描画

React を使うとき、そこには3つのDOMツリーがあると考えた方が良い。

  • 実DOM。ブラウザがレンダリングに使うもの。
  • 仮想DOM(A)。実DOMと同じであるものとして React 内部で保持されているもの
  • 仮想DOM(B)。state が更新されたときに生成されるもの。

React が state の変更を受けて実DOMを更新するとき、以下のステップを踏む

  • 新たな state を受けて、仮想DOM(B)が丸ごと生成される
  • 仮想DOM(A) と 仮想DOM(B) が比較され、仮想DOM(A)にどのような操作をすれば、仮想DOM(B)相当になるかの差分をコマンド化する
  • 実DOMに差分コマンドを適用し、実DOMが新たな state を反映したものになる
  • 仮想DOM(A) が 仮想DOM(B) で上書きされる

実際に実DOMに対して実行されるコマンドは

el.textContent = "piyo";
el.style.color = '#ff0011';

といった感じでおなじみの本当にネイティブな命令だが、React の価値は、

  • 変更を最低限にするための差分検知の仕組みがあることによって、実DOMの必要以上の更新が起こることがなく高速。
  • その差分が、常に stateprops に依存するために、普通に使っていれば状態と表示がズレることもない。

ということだ。

なぜjQueryが俺の魂を震えさせていたのか分からなくなった

上記からjQueryのそもそもの筋の良くなさに対して、さらに理解が深まった。

出来上がった実DOMに対してセレクタで走査して、アクセサで表示やイベントを手動で付け替えるというjQueryで当たり前に行われていたことを、サーバーサイドプログラミングで言うとこんな感じになる。

  • jspやテンプレートエンジンを用いてHTMLを生成する
  • DOMパーサーにHTMLを突っ込む
  • cssセレクタでエレメントを取得する
  • プロパティや内部のコンテントを書き換える
  • 書き換えたDOMからHTMLを再生成してレスポンスにする

それが筋のいいことではないという事は一目見ても分かるし「最初からテンプレートの定義で表示を出し分けようよ」となるのは必至であるはずなのに、何となくjQuery使っている時は、それが不思議とは思わなかった。

なぜ仮想DOMという概念が俺の魂を震えさせるのか

しかし今翻って React のコンポーネントを見てみると、そこにはデータ定義とテンプレートがあり、それらがツリーになっているという、自分の目からはサーバーサイドでよく見た光景に映った。なぜ今までこうなっていなかったのだろうとさえ思った。何も新しい事ではなかったのである。

SPAや巨大で複雑なアプリケーションに使えることも利点なのかもしれないが、 かつて我々がサーバーからHTMLを出力していた時代と同じようなパラダイムがフロントエンドに来て、

  • 階層化、モジュール化ができる奇麗で高速な設計
  • それをもれなく view に反映できる奇麗で高速な仕組み

が我々の魂に響くのではないかと思った。

付録

奇麗な世界の汚い話

コンポーネントshouldComponentUpdate というメソッドを生やす事で、stateprops の差分から

  • 新たな state を受けて、仮想DOM(B)が丸ごと生成される
  • 仮想DOM(A) と 仮想DOM(B) が比較され、仮想DOM(A)にどのような操作をすれば、仮想DOM(B)相当になるかの差分をコマンド化する

のステップを実行すべきか、すべきでないかを通知する事ができる。これは、stateprops が更新されても差分コマンドがゼロ(変更無し)になるパターンで探索をスキップできるのでパフォーマンスが上がる。しかしコードの可読性や保守性が下がったり、メンテナンス時に思わぬ罠になったりするというのは明らかだろう。。。

奇麗な世界の汚い話2

差分コマンドが最小限になるとは言っても document.createElement とか el.appendChild などのツリー構造の変更というのは相対的にコストが高く、表示/非表示を切り替えるのにエレメントの付け替えを行うより、差分が el.style.display = "block" or "none" で解決される形にしたほうが実DOM更新が高速化するなどの泥の紹介があった。こうなってくるとコードやテンプレートに意味がつき始めて闇が生成される。。。