Help us understand the problem. What is going on with this article?

Scala.js学習としてscalajs-reactのサンプルを読んでみた

More than 3 years have passed since last update.

東京に引っ越した@armorik83です。

引っ越してから長らくデスクが整っていなかったので、こういった記事を書く気力が無かったんだが、やっと整ったのでその勢いで。

今回のテーマは、Scala.js学習の一環としてscalajs-reactサンプルを読んでみたというもの。JavaScriptエンジニア目線でのScalaとして扱う。学習ノートで進めた順序通りに記事も執筆しているため多少構造が散漫になっていることは了承いただきたい。

どこから読めばいいか

ルートはここ。

./project/Build.scala

こういうのは勘で、とりあえずビルドツールであるsbt周りから。

一個一個が何やっているかは全く追ってないが、きっとJSコンテクストでのpackage.jsonなんだろうなと推測して終わり。

.enablePlugins(ScalaJSPlugin)

きっとこの一行が重要。plugins.sbtでプラグイン宣言。

./src/main/scala/todomvc

Scalaではこういう深いところにソースを置くのが作法らしい。

Main.scala

https://github.com/tastejs/todomvc/blob/24b83cdbd7d888540640bf3b64cf0f0348aa50d3/examples/scalajs-react/src/main/scala/todomvc/Main.scala

気になるところを見ていく。

package

Main.scala#L1

package todomvc
  • 名前空間宣言
  • Javaと同じくパッケージとしてまとめる

package foo {
  //
}
package foo

//

この2つは同じ。


package foo.bar {
  //
}
package foo.bar

//

この2つは同じ。


package foo {
  package bar {
    //
  }
}
package foo
package bar

//

この2つは同じ。こう書くものは次と同じ。(thx @xuwei_k)

package foo.bar

import foo._

たぶんpackage foo.barが一番使われるケース。

import

Main.scala#L3

import japgolly.scalajs.react._

JavaScript (ES2015)のようにimport 変数名 from 'モジュール名文字列'とはしない。import文末尾のものが使えるようになる。ここが_のときはwildcard。今回の場合、japgolly/scalajs/reactの全てを指すようだ。

個人的な印象としては、明示できる量の時は安易にwildcardにせず、使うものだけをその都度列挙した方が親切と感じた。おそらくチーム内で合意を取ったほうがいい。

package.scalaからindex.jsみたいな雰囲気を感じる。

メソッドや定数を定義できる「パッケージオブジェクト」とは
http://www.atmarkit.co.jp/ait/articles/1205/22/news134_2.html

object Main extends JSApp

Main.scala#L12

extends JSAppしておくと、そのobjectに宣言されたmain()がJavaScript側のエントリポイントとして呼ばれる。以前の記事で少し触れた。

main()

Main.scala#L46-L48

dom.document.getElementsByClassName("todoapp")(0)

この辺はJavaScriptコンテクストを彷彿とさせるが、2点注目すべき箇所がある。

文字列が'todoapp'ではなく"todoapp"

JavaScriptでは''""は等しく、文字列としてHTMLを書く際に属性をダブルクオートで記述するための利便から普段はシングルクオートで書くことが多かったが、Scalaでは明確に区別される。

ScalaにおいてシングルクオートはChar型リテラルであり、ダブルクオートが文字列(String型リテラル)となる。

index添字が[]ではなく()

java
a[0] = 123;
final int e = a[0];
scala
a(0) = 123
val e = a(0)

なるほど。なおセミコロンも不要。

React.scala

object React extends React

React.scala#L8-L9

main()を読むと、ここでReact.render()となったため実装側に移る。

object React extends React
trait React extends Object {
  //
}

という奇妙な記述が出てきたが、きっとtrait Reactobject Reactとして扱うという意味なんだろう。(それ以外にextends Reactの出元になりそうなもの無いし)

このReact.scalaだが、実装が一切ないので(そりゃそうだ、JavaScriptのライブラリなんだから)じゃあこのソースは一体何をしているかというと、型定義のように見える。TypeScriptでいう.d.tsだ。ということは、.d.ts.scalaに変換するやつなど誰かもう作っているだろうと思うと、やっぱりあった。クオリティは不明。

型定義なのは分かったが、これが実際のJSの何と紐付いているかはどこで取れるのだろうか。

Defining JavaScript interfaces with traits
http://www.scala-js.org/doc/calling-javascript.html

このjs.nativeのようだ。たぶん「同名前空間の同プロパティと紐付く」みたいな解釈でいいはず。

動かして確認

動かして何が格納されているか調べてみるため、fastOptJSで読めるJSにコンパイルする。方法は過去の記事でまとめた。

$ sbt
> fastOptJS

およそ47,000行のJSが出力される。atomでは開いてすぐ固まり画面がまったくスクロールしなくなったので、TextEdit.appやパフォーマンスに取り組んでいるエディタで開くといい。

todomvc-opt.js
$c_Ltodomvc_Main$.prototype.main__V = (function() {
  $g["React"]["render"](this.router$1, $g["document"]["getElementsByClassName"]("todoapp")[0])
});

およそ12,500行目付近にこのような出力がある。$gwindow、そしてそこにwindow.Reactとなる形で$g["React"]となっている。Scala.jsコンパイル後のJSは大半の変数名が名前空間付きなので非常に長いが、長いだけでJSとしては読めるレベルである。

このReactは格納済みだが、Browserifynpm iもしていないのにどこから?と調べてみたら、先のBuild.scalaに答えがあった。

Build.scala
def useReactJs(scope: String = "compile"): PE =
    _.settings(
      jsDependencies += "org.webjars" % "react" % "0.12.1" % scope / "react-with-addons.js" commonJSName "React",
      skip in packageJSDependencies := false)

sbtで依存解決しているらしい。sbtログにも次のような表示がみられる。

Terminal.app
[info] downloading https://repo1.maven.org/maven2/org/webjars/react/0.12.1/react-0.12.1.jar

この実体はどこにダウンロードされたのか調べると、~/.ivy2/cache/org.webjars/react/jars/react-0.12.1.jarに居た。WebJarsとはMavenなどのJavaエコシステムにおいてJSやCSSを解決するためのリポジトリらしく、有名どころはほとんどが保守されている。AngularJSもあった。

ivyはApache Ivyといいsbtが利用している依存解決の実装のことだそうだ。正直MavenやIvyと言われてもさっぱりだが、npmやbowerのようなものと認識している。

2014年4月
exe/dmgしか知らない人のためのインストール/パッケージ管理/ビルドの基礎知識 (3/4)
http://www.atmarkit.co.jp/ait/articles/1403/31/news032_3.html

ということで、このreact-0.12.1.jar内に生のJSソースが入ってたため、これを解決している。

JSExportをCommonJSモジュールとして扱う方法もあるため、Browserify側に寄せることも可能だろうと見ている。アプリケーションや開発リソースによってはWebJarsに寄せきるのもアリかもしれない。(React実装を全てScalaに寄せるとJSXとして書けないという問題が起きるので、コストと相談)

CTodoList.scala

Reactの挙動の詳細は本稿では割愛する。ざっくり言うと、HTML内の<section class="todoapp"></section>にて指定のReact Componentが展開される。その指定されたComponentがここではCTodoListだ。

CTodoList.scala

非常に奇妙なのが、このソースに現れるrenderだ。

CTodoList.scala#L59-L80
def render: ReactElement = {
  val todos           = $.state.todos
  val filteredTodos   = todos filter $.props.currentFilter.accepts

  val activeCount     = todos count TodoFilter.Active.accepts
  val completedCount  = todos.length - activeCount

  <.div(
    <.h1("todos"),
    <.header(
      ^.className := "header",
      <.input(
        ^.className     := "new-todo",
        ^.placeholder   := "What needs to be done?",
        ^.onKeyDown  ~~>? handleNewTodoKeyDown _,
        ^.autoFocus     := true
      )
    ),
    todos.nonEmpty ?= todoList(filteredTodos, activeCount),
    todos.nonEmpty ?= footer(activeCount, completedCount)
  )
}

Reactを理解していると、これがJSXに相当することは察しがつくがそれにしても奇妙な面をしている。1つずつ見ていく。

<.div

JSXの<div>のように見えるが、そもそもここでいう<は何か。追ってみるとscalajs-react側に実装があった。

package.scala#L33-L45

package.scala#L33-L45
object prefix_<^ extends Base {
  @inline def < = Tags
  @inline def ^ = Attrs
}

object svg {
  object all extends Base with SvgTags with SvgAttrs

  object prefix_<^ extends Base {
    @inline def < = SvgTags
    @inline def ^ = SvgAttrs
  }
}

<はここで宣言されている。下には^もいた。想像通り、タグや属性のためのものだ。JavaScriptでは変数名に使用できる記号は$_に限られているが、Scalaはけっこうなんでもありだ。

Scala用メソッド名の変換
http://www.ne.jp/asahi/hishidama/home/tech/scala/reflect.html#h_NameTransformer

~~>?

これまた奇妙だ。これはScalazReact.scala#L25-L39にある。

ScalazReact.scala#L25-L39
@inline implicit final class SzRExt_Attr(private val _a: Attr) extends AnyVal {

  @inline final def ~~>(io: => IO[Unit]): TagMod =
    _a --> io.unsafePerformIO()

  @inline final def ~~>[N <: dom.Node, E <: SyntheticEvent[N]](eventHandler: E => IO[Unit]): TagMod =
    _a.==>[N, E](eventHandler(_).unsafePerformIO())

  @inline final def ~~>?[T[_]](t: => T[IO[Unit]])(implicit o: Optional[T]): TagMod =
    _a --> o.foreach(t)(_.unsafePerformIO()) // This implementation keeps the argument lazy
    //o.fold[IO[Unit], TagMod](t, ~~>(_), EmptyTag)

  @inline final def ~~>?[T[_], N <: dom.Node, E <: SyntheticEvent[N]](eventHandler: E => T[IO[Unit]])(implicit o: Optional[T]): TagMod =
    _a.==>[N, E](e => o.foreach(eventHandler(e))(_.unsafePerformIO()))
}

ここでメソッドとして宣言されているが、使うときは^.onKeyDown ~~>? handleNewTodoKeyDown _,のように扱われる。これを中置記法という。

@inline

inlineについては、はっきりと分からなかったのだが、おそらくC言語などのインライン展開を意味するのではないだろうか。なお@はScalaではAnnotationsと呼ばれる。一方JavaScriptの次期仕様としては、Decoratorsなるものが提案されている

:=

これについては悩んだ。sbtにもScala.jsにも登場するためどちらを使っているか追いきれなかったが、おそらくScala.js側のdefだろう。

TreeDSL.scala#L48-L49

今回はここまで

Scalaと出力JS読みながら、実際に動かしながら、それぞれ見ていった。気になったところは順次潰していったが、まだまだ興味は尽きない。implicitcase classなどは今後に持ち越す。

最後に実際に動いているものを。

http://todomvc.com/examples/scalajs-react/#/

「ああ…」としか言えん。

以上。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away