Scala.jsでもReactを使いたい

はじめに

Scalaが好きすぎてフロントエンドでも使いたいのでScala.jsに入門してみました。

必須環境

Scala.jsのチュートリアル曰くsbtとかnode.jsを入れなさいということなのでインストールしましょう。

また、npmパッケージのキャッシュとかバージョンの固定とかしてくれるYarnも一緒に入れておきます。直接使うことはありませんが、scalajs-bundler経由で利用します。

Hello World

scala-seed.g8からプロジェクトをシードして、ちょろっと書き換えてScala.js用のプロジェクトを作成します。

Hello world · jyuch/scalajs-react-tutorial@60aaf73 · GitHub

この状態で

> sbt run

とするとコンソールにHello worldと表示されますが、何となく味気ないです。

Hello World in browser

今度は適当なHTMLを書いて生成されたJavaScriptを読み込んでみましょう。

Hello World in browser · jyuch/scalajs-react-tutorial@2c18e9d · GitHub

> sbt fastOptJS

Scalaのコードをトランスパイルし、ブラウザでindex.htmlを開きます。

f:id:jyuch:20180807221154p:plain

ちゃんとScalaコードが(JavaScriptにトランスパイルされて)ブラウザ上で動いています。

Hello World on DOM

今度はReactを使ってDOMの差し替えを行ってみましょう。

scalajs-reactbuild.sbtの依存ライブラリに追加します。

また、Reactの依存ライブラリ諸々を1つのjsファイルに押し固めたいのでscalajs-bundlerプラグインを追加します。

Hello World on DOM · jyuch/scalajs-react-tutorial@6a064bb · GitHub

そしてこうやって

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The Scala.js Tutorial</title>
</head>
<body>

<div id="playground"></div>

<script type="text/javascript" src="./target/scala-2.12/scalajs-bundler/main/scalajs-react-tutorial-fastopt-bundle.js"></script>
</body>
</html>

こうして

package react4s

import org.scalajs.dom.document
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._

object Hello {
  val playground = document.getElementById("playground")

  def main(args: Array[String]): Unit = {
    val NoArgs =
      ScalaComponent.builder[Unit]("No args")
        .renderStatic(<.div("Hello World"))
        .build

    NoArgs().renderIntoDOM(playground)
  }
}

トランスパイルして

> sbt fastOptJS::webpack

こうです。

f:id:jyuch:20180807224933p:plain

Todo list

せっかくReactを使っているので、SPAっぽいことをやってみましょう。というわけで(消せない)Todoリストです。

Todo list · jyuch/scalajs-react-tutorial@a224ac7 · GitHub

f:id:jyuch:20180807235605p:plain

package react4s

import org.scalajs.dom.document
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._

object Hello {
  val playground = document.getElementById("playground")

  def main(args: Array[String]): Unit = {
    val Example = ScalaComponent.builder[Unit]("Example")
      .initialState(State("", List("Hello World")))
      .renderBackend[Backend].build

    Example().renderIntoDOM(playground)
  }

  case class State(item: String, items: List[String])

  class Backend($: BackendScope[Unit, State]) {
    def render(s: State): VdomElement = {
      <.div(
        <.form(^.onSubmit ==> handleSubmit,
          <.input(^.onChange ==> onChange, ^.value := s.item),
          <.button("Add item")
        ),
        <.ul(
          s.items map { it => <.li(it) }: _*
        )
      )
    }

    def handleSubmit(e: ReactEventFromInput): Callback = {
      e.preventDefaultCB >> $.modState(s => State("", s.item :: s.items))
    }

    def onChange(e: ReactEventFromInput): Callback = {
      val v = e.target.value
      $.modState(_.copy(item = v))
    }
  }

}

<.div^.onSubmitのような面妖なDSLが出てくると『うわっ、こっわ』となりますが、しばらく書いているとこんなものかなーと思えてくるので慣れって怖いですね。

github.com