scalajs-reactで作るフロントエンド / Frontend With scalajs-react

E8a1ea1668c402ae8cd5baea8f7b7a97?s=47 yuiwai
September 30, 2019

scalajs-reactで作るフロントエンド / Frontend With scalajs-react

E8a1ea1668c402ae8cd5baea8f7b7a97?s=128

yuiwai

September 30, 2019
Tweet

Transcript

  1. scalajs-reactで作る フロントエンド Yuichiro.iwai 2019/09/30 - Fun Fun Functional (3)

  2. 自己紹介 岩井 雄一郎(SUGAR株式会社) Scalaをこよなく愛する37歳、道産子、2児の父 長らくソシャゲ業界でサーバサイドの開発をしてました。 4月からSUGARで、主にAndroidの開発をしています。 趣味はダーツ、ギター、将棋

  3. 今日のお話 このたび、フロントエンド(管理画面的なもの)を書くかも?ということになりまし て、かねてより愛用していたScala.jsで、Reactを利用するアプローチについて調 査しましたので、その概念をご紹介したいと思います。 (個人的に、もっとScala.jsが広まればいいな、と願っており、その宣伝も兼ねて おりますこと、ご了承ください)

  4. Scala.jsとは? • Scalaで書いてJSにコンパイルされる、いわゆるAltJSの一種 • Scalaで書いたコード+一部のJavaライブラリ*が動く • Scala.js用にビルドされたScalaライブラリが利用可能 https://github.com/scala-js/scala-js *Scalaの動作の為に必要な Java依存部分を、Scala.js用に書いたもの(ライセンス的な)

  5. Reactとは? • Facebook社が開発したJS向けのUIライブラリ • 状態とViewを分離し、仮想DOMを用いて効率的な描画を実現 • コンポーネントベース ◦ 再利用可能で副作用の無い小さな単位に切り分ける https://github.com/facebook/react/

  6. Reactとは? State VDOM HTML ある状態の入力に対して、 VDOMが一意に決まり、結果として HTMLがレンダリングされる

  7. Reactとは? State VDOM HTML modified! 状態の変化が通知されると、 VDOMが更新されて

  8. Reactとは? State VDOM HTML modified! diff 以前のVDOMとの変更差分が算出され、それが HTMLへと反映される

  9. scalajs-reactとは? • Scala.js用のReactライブラリ • Scalaで書きやすいような工夫がなされている • 副作用を局所化、型安全、Functionalなアプローチ • Routerなどの拡張も含んでいる https://github.com/japgolly/scalajs-react

  10. scalajs-reactで扱う 主要な概念を見ていきます

  11. State • Immutableなデータ構造 ◦ 更新時は、新しいStateを作り直す ◦ case classとの相性が非常に良い • Component内の動的な性質を保持する

    ◦ ユーザの操作に応じて変化する値 ◦ API通信など外部から取得した値 ◦ 時間経過で随時変化する値 ...など • 基本的には最上位の Componentにのみ持ちたい(次ページへ) case class State(wallet: Wallet) { def charge(amount: Point): State = copy(wallet = wallet.charge(amount)) } case class Wallet(balance: Point) { def charge(amount: Point): Wallet = copy(balance = balance.add(amount)) } case class Point(value: Long) extends AnyVal { def add(that: Point): Point = ... }
  12. Root以外のComponentがStateを持つべきか? • 基本的には上位Componentから受け取ったPropsにのみ依存し、結果を決めたい ◦ データの流れを単方向にし、副作用を排除することで、再利用しやすくなる ◦ 下位Componentで変化したStateを上位に同期する流れは、構造が複雑化する • ただし、そのComponent内で完結し、揮発してしまっても構わない Stateなら、各Componentで

    持った方が素直に書けると考えています ◦ 例えば、編集中のフォームのデータ、その画面内だけで有効な設定など
  13. Props • Immutableなデータ構造 ◦ やはりcase classとの相性が良い • Componentの初期化時に渡される • Componentの静的な性質を保持する

    ◦ 上流にイベントを伝搬するハンドラ ◦ Globalな設定など ◦ 親Componentから受け継いだ属性 ...など case class Props( wallet: Wallet, // 状態の参照 settings: GlobalSetting, // 何らかの設定など handler: Action => Callback // イベントハンドラ ) // VDOMの中で使う例 (p: Props) <.div( s“残高: ${p.wallet.balance.value}”, <.button( ^.onClick --> p.handler(Charge), “チャージ” ) )
  14. Component • singleton objectとして定義を格納する • StateとPropsを定義 ◦ StateはComponent内で可変 ◦ PropsはComponent内で不変

    • Backend(後述)を定義 object WalletComponent { case class Props(...) case class State(...) class Backend(...) { ... } } 定義 Component 初期化 Props 実体
  15. Component Backend • Component定義内にBackendというクラスを作成 ◦ このクラス内の処理が Componentの実体となる • renderメソッドを定義 ◦

    State/Propsを受け取りVDOMを返す Props State Backend render() VDOM 状態の入力から、描画への出力を行う、いわば橋 渡し的な存在で、UIライブラリとしての中核を担う。
  16. BackendScope • Backendがコンストラクタに受け取る ◦ このインスタンスを介して Stateを 操作したり、Propsを参照したり • Props/Stateを型パラメータに持つ ◦

    BackendScope[Props, State] • renderメソッドの外からState/Propsに アクセスする場合、このインスタンス を経由し、Callbackとして扱う ◦ Callbackが評価されるタイミングで その時点のState/Propsを参照する // bsはBackendScopeのインスタンス bs: BackendScope[Props, State] // 現在のStateを受け取り新しいStateに更新 // modState(f: State => State): Callback bs.modState( s => ...) // bs.propsはPropsの参照を表す // CallbackTo[Props] // // このmapは // map[A](f: Props => A): CallbackTo[A] bs.props.map { p => ... }
  17. 階層化されたComponent • Componentは子として別なComponentを 持つことができる • 親から子へは、Propsを通じてデータを 受けわたす • 親のState(もしくはProps)が変化することで 子に変更が伝搬される

    Parent Child Props Props State
  18. VDOM • 属性値を持った要素のツリー構造 ◦ 実質、HTMLのタグに相当 • ユーザのインタラクションを イベント(後述)として受け取る // <.xxx(...)

    で要素(タグ)を定義 <.div( // ^.xxx := ... で属性を定義 ^.id := “someId”, <.button( // --> イベントハンドラ ^.onClick --> { … }, // 文字列はそのまま子要素扱い “Click Me!” ), // Componentを子要素にする OtherComponent.Props(...).render )
  19. Event(Handler) • クリックやキー入力などユーザ操作で発生 • EventをトリガーにCallback(後述)を返す ハンドラを実装することでリアクション ◦ ハンドラはEventオブジェクトを受け取る こともできる //

    以下はVDOMの属性定義の文脈の前提 // -->[A](cb: => CallbackTo[A]): TagMod ^.onClick --> { ... } // ==> はイベントオブジェクトを受け取る // ==>[A](f: Event => CallbackTo[A]): TagMod ^.onKeyDown ==> { e => // ここでpersist()を呼んでおかないと // このCallbackが評価される時点で // 正しいイベントオブジェクトにアクセス // できない場合がある e.persist() … } ※TagModはVDOM中の構成要素として展開可能な型 イベントオブジェクトを受け取るケースでは、Callbackが 遅延評価されることと、イベントオブジェクトが再利用さ れることの兼ね合いで、persistを呼んでおかないと正常 に動作しないという罠が
  20. Callback,CallbackTo[T] • 副作用を包み込む ◦ Stateの書き換え ◦ 外部通信などの非同期処理 ◦ ログ出力やAlertの表示 •

    CallbackTo[T]はT型の結果を包んだもの • CallbackはCallbackTo[Unit]へのalias • >>や>>=などで合成できる • 非同期版のAsyncCallback[A]もある val cba: CallbackTo[A] = … val cbb: CallbackTo[B] = … val a2cbb: A => CallbackT[B] = … val asyncCbc: AsyncCallbackTo[C] = ... // シーケンシャルに実行 // >>[B](runNext: CallbackTo[B]): CallbackTo[B] cba >> cbb // >>= はflatMapのalias // >>=[B](f: A => CallbackTo[B]): CallbackTo[B] cba >>= a2cbb // AsyncCallback同士で処理する必要がある // callback.asyncでAyncCallbackに変換 cba.async >> asyncCbc
  21. Component 全体像 React.js scalajs-react HTML VDOM User Backend State Event

    Props Initialize View Action render() EventHandler Callback Modify Event
  22. まとめ • scalajs-reactは、Scalaを使ってFunctionalなアプローチでWebフロントエ ンドを書けるソリューションです。 • Scala好き、あるいはScalaに興味があり、フロントエンドもScalaで書いてみ たいという方にはオススメです。 • Scala.jsはWebフロント以外のユースケースもあります! 興味を持っていただけたら、幸いです。

  23. ご清聴ありがとうございました