Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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 = ... }

Slide 12

Slide 12 text

Root以外のComponentがStateを持つべきか? ● 基本的には上位Componentから受け取ったPropsにのみ依存し、結果を決めたい ○ データの流れを単方向にし、副作用を排除することで、再利用しやすくなる ○ 下位Componentで変化したStateを上位に同期する流れは、構造が複雑化する ● ただし、そのComponent内で完結し、揮発してしまっても構わない Stateなら、各Componentで 持った方が素直に書けると考えています ○ 例えば、編集中のフォームのデータ、その画面内だけで有効な設定など

Slide 13

Slide 13 text

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), “チャージ” ) )

Slide 14

Slide 14 text

Component ● singleton objectとして定義を格納する ● StateとPropsを定義 ○ StateはComponent内で可変 ○ PropsはComponent内で不変 ● Backend(後述)を定義 object WalletComponent { case class Props(...) case class State(...) class Backend(...) { ... } } 定義 Component 初期化 Props 実体

Slide 15

Slide 15 text

Component Backend ● Component定義内にBackendというクラスを作成 ○ このクラス内の処理が Componentの実体となる ● renderメソッドを定義 ○ State/Propsを受け取りVDOMを返す Props State Backend render() VDOM 状態の入力から、描画への出力を行う、いわば橋 渡し的な存在で、UIライブラリとしての中核を担う。

Slide 16

Slide 16 text

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 => ... }

Slide 17

Slide 17 text

階層化されたComponent ● Componentは子として別なComponentを 持つことができる ● 親から子へは、Propsを通じてデータを 受けわたす ● 親のState(もしくはProps)が変化することで 子に変更が伝搬される Parent Child Props Props State

Slide 18

Slide 18 text

VDOM ● 属性値を持った要素のツリー構造 ○ 実質、HTMLのタグに相当 ● ユーザのインタラクションを イベント(後述)として受け取る // <.xxx(...) で要素(タグ)を定義 <.div( // ^.xxx := ... で属性を定義 ^.id := “someId”, <.button( // --> イベントハンドラ ^.onClick --> { … }, // 文字列はそのまま子要素扱い “Click Me!” ), // Componentを子要素にする OtherComponent.Props(...).render )

Slide 19

Slide 19 text

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を呼んでおかないと正常 に動作しないという罠が

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Component 全体像 React.js scalajs-react HTML VDOM User Backend State Event Props Initialize View Action render() EventHandler Callback Modify Event

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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