om next ~react.jsを超えて
TRANSCRIPT
Om Next ~React.js を超えて
2017/03/14
nishi-shinju-clojure#1
@goronao
ハッシュタグ : #nishi-shinju-clojure
自己紹介
• @goronao
• Clojure / Script 歴約二年
• 好きな Project : component / duct / om / reagent
• 最近は job-streamer の保守開発をやってますhttps://github.com/job-streamer
Om : React.js の ClojureScript ラッパー +α(om-0.9)
Om Next : Om の後継 (om-1.0)
• 今日はこの流れを前提に Om Next がどの様にReact.js を超えているか話します
※ React.js 自体や周辺の JS ライブラリには触れますが詳述はしません(出来ません)
Om Next とは
React.js ➡ React.js ➡
cljs+α cljc+α+α
React.js
Om Om Next
Om Next とは
• Om Next の主要な仕様• UI
• グローバル状態管理
• クライアントサーバアーキテクチャ
• サーバサイドサポート
• Reconciler
UI
• Om Next における React Component 定義• om.next/defui というマクロで定義する
• React Component + メタデータ
UI
• 赤枠内が React.js のrender 実装• その他 lifecycle methods(componentDidMount等)も実装可能
UI
• 赤枠内は後述• React Component 以外に描画データ用のメタ情報を定義できる
UI
• これは単純に React.js のラッパーとしての機能
• React Component のデータ管理に関する問題• React Component は2種類のデータを持つ
• Props : 親から受け継がれる Immutable なデータ
• State : Component 自体が保持する Mutable なデータ更新が再描画の条件となる
• State(状態)が複雑性の原因• React Component それぞれが状態を持つため管理が難しい
• 全体の情報を参照できない
• 自分以外の Component の状態更新
UI
State
State
State State State
State
Props
Props
Props
Props Props
Components
描画
UI
State
State
State State State
State
Props
Props
Props
Props Props
update
Components
UI
State
State
State State State
State
Props
Props
Props
Props Props
update
Components
再描画
UI
State
State
State State State
State
Props
Props
Props
Props Props
update
Components
再描画
グローバル状態管理
• 状態をアプリケーション全体で1つだけ持つ• グローバル状態はツリー構造を取りこれを参照・更新• Component はPropsで受け渡されるデータを描画すればよいだけ• 状態の更新に伴い Component ツリー上の対応箇所を再描画
• React.js の場合• Redux を組み合わせて実現
• Om(旧) の場合• グローバル状態管理をサポート• 再描画する Component を決定するために、状態と Component ツ
リーの対応を定義する cursors という仕組みも導入
グローバル状態管理(Om)
Props
Props
Props
Props Props
Components State
cursorsにより対応
Props
cursor cursor cursor
cursor cursor
cursor
Props
Props
Props
Props Props
Components State
PropsMount描画
グローバル状態管理(Om)
Props
Props
Props
Props Props
Components State
Props
グローバル状態管理(Om)
cursorsによって対応
transact!
cursor
Props
Props
Props
Props Props
Components State
再描画Props
グローバル状態管理(Om)
cursorsによって対応
cursor
transact!
グローバル状態管理
• Om(旧) の場合• グローバル状態の導入で状態管理はシンプルに
• 状態と Component ツリーの対応関係を cursors で定義
• しかしグローバル状態と Component が密結合してしまう問題がある• cursor はグローバル状態のサブセットでありツリー構造を取る
• UI はツリー構造の状態に紐づけられるとは限らない(むしろ稀)
• Om Next の場合• グローバル状態管理を同様にサポート
• 次に触れるクライアントサーバアーキテクチャによって状態をComponent から分離し上記の問題の解決を図る
クライアントサーバアーキテクチャ
1. クライアントサイドにサーバ(の様なもの)を設ける• 必要な情報操作(Read, Mutate)はルーター(Parser)にクエリを発
行して対応する処理を実行
• 直接 Component から状態を操作しないため両者の分離が図れる
parser定義(実態は multimethod) read, mutate クエリ
クライアントサーバアーキテクチャ
• Read クエリは UI に定義する• View に全く関係ないデータは取る意味ない
• グローバル状態からの単純な抜き出しなら Parser の実装不要
クライアントサーバアーキテクチャComponents State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
クライアントサーバアーキテクチャComponents State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
クライアントサーバアーキテクチャ
Props
Props
Props
Props Props
Components State
Query
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
Props
Read:products/list:products/cart:products/purchase
クライアントサーバアーキテクチャ
2. グローバル状態をグラフ構造で表現する• グラフ構造を表現するためにはデータの一意性の定義が必要
• これも UI に定義する
• (下記例) product は :product/number で一意
クライアントサーバアーキテクチャ
• 一意性定義を基に Om Next はグローバル状態をグラフ構造にノーマライズ• (下記例) name で一意にノーマライズ
クライアントサーバアーキテクチャ
• 以上 により Component と状態を分離した上で、グラフ構造の状態と Component ツリーの対応を表現できる• cursors の状態サブセットによる対応ではなく、
クエリによる状態参照でグラフと Component が紐づく
• UI (Component) はクエリと一意性定義を持つ ... ①
• クエリも UI (Component) への参照を持つ ... ②
①
②
クライアントサーバアーキテクチャ
Props
Props
Props
Props
Props
Components State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
Props
transact!
クライアントサーバアーキテクチャ
Props
Props
Props
Props
Props
Components State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
Props
クライアントサーバアーキテクチャ
Props
Props
Props
Props
Props
Components State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
Props
再描画
クライアントサーバアーキテクチャ
• React.js の場合• Redux / GraphQL あたりまで組み合わせて実現(ざっくり)
• GraphQL も参照型を持ちグラフ構造が表現可能
• 通常、GraphQL はサーバサイドとの通信に用いる
• Om Next の場合• サーバサイドもサポートする
サーバサイドサポート
• サーバサイド Parser• クライアントサイドと同じ構文でサーバサイドにも Parser を定義可能
• 単一のエンドポイントで公開
• エンドポイントの呼び出し定義と、サーバサイド Parser を呼び出すルートの定義をする(:remoteキーを含んだマップを返すだけ)
サーバサイドサポート
Components State
Read:products/list ➡ {:remote true}:products/cart:products/purchase
Parser(Client Side)
Mutate'cart/add-product'cart/remove-product‘products/purchase
➡ {:remote true}
Query Query
Query
Read:products/list
Mutate'products/purchase
Parser(Server Side)
transact!
サーバサイドサポート
Components StateParser(Client Side)
Mutate'cart/add-product'cart/remove-product‘products/purchase
➡ {:remote true}
Query Query
Query
Read:products/list
Mutate'products/purchase
Parser(Server Side)
Props
Read:products/list ➡ {:remote true}:products/cart:products/purchase
自動マージ
サーバサイドサポート
• Om Next の場合• サーバサイドのRead クエリ結果はグローバル状態へ自動マージ
• Parser とグローバル状態が一元管理されているため、遅延読み込みや楽観的更新も簡単に実現可能
• React.js の場合• Redux / GraphQL / Relay を組み合わせて実現(ざっくり)
• 当然サーバサイド (Node.jsなど) も別に用意が必要
Reconciler
• 紹介してきた機能を実現するためには全体の統合が必要
• Om Next でそれを担っているのが Reconciler• 諸々の定義を渡して生成
• グローバル状態、parser、サーバとの通信方法
• Component ツリー構築時に渡すと後はよきに計らってくれる
Query ServerReconciler
Components State
Read
Reconciler
Mutate
Read
/api/query
Mutate
React.js / Om / Om Next 比較
• Om Next ~ React.js + Redux + GraphQL + Relay + Node.js?• 組み合わせを選択しなければならないのはそれ自体 React.js の
Cons ではある(Falcor vs. GraphQL でも同じような議論)
React.js Om Om Next
UI - React.js 同等 React.js 同等
グローバル状態管理 なし(+Redux が必要)
あり(ツリー構造)
あり(グラフ構造)
クライアントサーバアーキテクチャ
なし(+Redux / GraphQL が必要)
なし あり
サーバサイドサポート
なし(+Redux / GraphQL
/ Relay / Node.js が必要)
なし あり
Om Next 独自機能
• React.js+周辺ライブラリで実現できない機能も存在する
• JVM でサーバサイドレンダリング• 俺達には cljc がある!• ui, parser, reconciler いずれもサーバサイド(JVM)上に実装可能
https://designudge.org/ja/stories/1481369485901_fe99b3b7/
• 描画効率の改善• Incremental Rendering• Reconciler が状態とComponentの関係を全て把握(Indexer)
しているためより効率的な再描画が可能https://anmonteiro.com/2016/09/om-next-internals-incremental-rendering/
Incremental Rendering
Props
Props
Props
Props
Props
Components State
Query
Read:products/list:products/cart:products/purchase
Parser
Mutate'cart/add-product'cart/remove-product'products/purchase
QueryQuery
Query Query
Query
Props
再描画
Om Next Cons
• Clojurian 以外へのハードル爆上げ
• 学習曲線が急すぎる• 機能多すぎる
• GraphQL, Relay 等を先に学習しないとアーキテクチャの理解が厳しい
• ロードマップが不明• もうコンセプトにぶれはなさそう
• Circle CI で使ってるし...
• 状態管理方式の選択• Component 状態は閉じられているわけではないのでアーキテクチャを
理解しないとこれまで紹介した問題は依然発生する
Om Next の採用シーン
• クライアント側の状態管理が複雑なアプリケーション• 多様なソース(サーバからのデータ、クライアントオンリーデータ、
Web Storageに保存されたデータ)からのデータを扱う
• それぞれの更新も頻繁に発生
• Om Next では Reconciler が上手く統合してくれる
• CircleCI はまさにこれ
• UI の変更が多いアプリケーション• Om Next は Component 自身が自分の必要とするデータを宣言してい
るため変更に強い
• (既存の Clojure / Script 資産がある場合...)
まとめ
• Om Next は二つの意味で React.js を超えていると言える• React.js 単体で実現できない機能(Redux, GraphQL, Relayの責務)
をサポート
• 更にそれらの組み合わせでも実現できない魅力的な機能が存在
• 採用にやや不安はあるものの、最新のフロントエンド界隈の知識が得られるため学んでおいて損はない
Try Om Next!
ありがとうございました