2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ScalaJSによるReact

Posted at

scalajs-reactを使う

ScalaJSでReactを使う場合、scalajs-reactというFacadeを使うのが一般です。残念なことにscalajsのバージョン1.0系以降には対応していないので0.6系を使います。

準備

前のエントリのようにプロジェクトのひな型から作ります。
今回はWorkbenchとscalajs-bundlerのプラグインを使います。
WorkbenchはScalajsのアプリ用にローカルサーバを立ち上げて、HTMLを表示してくれます。scalajs-bundlerを使うと、jsDependencyとwebjarsを使って依存関係を解決するというオールドスクールなやり方をしないで済みます。Webjarsって便利だけど、参照がうまくいかなかったりするのでこの方が便利です。

sbt new scala/scala-seed.g8
project/plugin.sbt
// Sbt plugins
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.26")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.14.0")
addSbtPlugin("com.lihaoyi" % "workbench" % "0.4.1")
build.sbt
name := "reactjs-test"

version := "0.1"

scalaVersion := "2.12.6"

lazy val root =  (project in file("."))
  .settings(
    scalaJSUseMainModuleInitializer := true,
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "0.9.6",
      "com.github.japgolly.scalajs-react" %%% "core" % "1.3.1"
    ),
    npmDependencies in Compile ++= Seq(
      "react" -> "16.5.1",
      "react-dom" -> "16.5.1")
  )
  .enablePlugins(ScalaJSPlugin, WorkbenchPlugin, ScalaJSBundlerPlugin)

これで準備は完了です。
公式サイトに倣って、簡単なサンプルを実装します。

サンプルアプリ(Timer)

簡単な、というより公式のでもサイトのサンプルをそのまま実装しています。一応、内部でStateで状態管理しているのでReactっぽいコンポーネントなので、どのように実装すればよいかはわかるかと思います。

scalajs/Timer.scala
package scalajs

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

import scala.scalajs.js

object Timer{

  def main(args: Array[String]): Unit = {

    val Timer = ScalaComponent.builder[Unit]("Timer")
      .initialState(State(0))
      .renderBackend[Backend]
      .componentDidMount(_.backend.start)
      .componentWillUnmount(_.backend.clear)
      .build

    Timer().renderIntoDOM(document.body)
  }

  case class Point(x: Int, y: Int){
    def +(p: Point) = Point(x + p.x, y + p.y)
    def /(d: Int) = Point(x / d, y / d)
  }

  case class State(secondsElapsed: Long)

  class Backend($: BackendScope[Unit, State]) {
    var interval: js.UndefOr[js.timers.SetIntervalHandle] =
      js.undefined

    def tick =
      $.modState(s => State(s.secondsElapsed + 1))

    def start = Callback {
      interval = js.timers.setInterval(1000)(tick.runNow())
    }

    def clear = Callback {
      interval foreach js.timers.clearInterval
      interval = js.undefined
    }

    def render(s: State) =
      <.div("Seconds elapsed: ", s.secondsElapsed)
  }
}

最初のTimerの変数宣言のところで、Reactのコンポーネントを指定しています。最初の値である0をCase ClassのStateとして生成し、initialeStateメソッドの引数としています。次のrenderBackendは下に示しているコンポーネントの動きを記述しているクラスを指定しています。同クラスのrender内に書かれている.<はScalajs-reactのDSLになります。
次のチェーンメソッドのcomponentDidMountcomponentWillUnmountはReactコンポーネントのライフサイクルのフックになっているようですね。最後にbuildして使えるようにしています。

これをHTML内に挿入するのはrenderIntoDOMメソッドを使います。普通のReactだとReactDOM.render()とかやるところですね。

これを表示するHTMLは以下の感じです。何の変哲もないですが、scalajs-bundlerを使っているので、参照するJSファイルのパスが少し違います。ここは要チェックです。

index-dev.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript" src="/workbench.js"></script>
<script type="text/javascript" src="./target/scala-2.12/scalajs-bundler/main/reactjs-test-fastopt-bundle.js"></script>
</body>
</html>

scalajs-bundler含めてのコンパイルとしては、以下のコマンドで行います。

fastOptJS::webpack

Workbenchが立ち上がっているので、http://localhost:12345/index-dev.htmlにアクセスすれば、単に時間をカウントするだけのアプリが表示されます。

所感

まぁ、できることはできますが、Scalajsに慣れていないこともあってとにかくとっつきにくかったです。DSLも相まって混乱を誘います。ただ、よくよく見ると合理性のあるつくりになっており、ScalaでPlayとの連携も容易なので、少し大きなプロジェクトだといい感じに使えるのだと信じています。

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?