Scala.js 学習として scalajs-react のサンプルを読んでみた
東京に引っ越した@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
気になるところを見ていく。
package
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
import japgolly.scalajs.react._
JavaScript (ES2015)のようにimport 変数名 from 'モジュール名文字列'
とはしない。import 文末尾のものが使えるようになる。ここが_
のときは wildcard。今回の場合、japgolly/scalajs/react
の全てを指すようだ。
個人的な印象としては、明示できる量の時は安易に wildcard にせず、使うものだけをその都度列挙した方が親切と感じた。おそらくチーム内で合意を取ったほうがいい。
-
import org.scalajs.dom
は、きっとここ
package.scala
からindex.js
みたいな雰囲気を感じる。
メソッドや定数を定義できる「パッケージオブジェクト」とは
http://www.atmarkit.co.jp/ait/articles/1205/22/news134_2.html
object Main extends JSApp
extends JSApp
しておくと、そのobject
に宣言されたmain()
が JavaScript 側のエントリポイントとして呼ばれる。以前の記事で少し触れた。
main()
dom.document.getElementsByClassName("todoapp")(0)
この辺は JavaScript コンテクストを彷彿とさせるが、2 点注目すべき箇所がある。
文字列が'todoapp'ではなく"todoapp"
JavaScript では''
と""
は等しく、文字列として HTML を書く際に属性をダブルクオートで記述するための利便から普段はシングルクオートで書くことが多かったが、Scala では明確に区別される。
Scala においてシングルクオートは Char 型リテラルであり、ダブルクオートが文字列(String 型リテラル)となる。
index 添字が[]
ではなく()
a[0] = 123;
final int e = a[0];
a(0) = 123
val e = a(0)
なるほど。なおセミコロンも不要。
React.scala
object React extends React
main()
を読むと、ここでReact.render()
となったため実装側に移る。
object React extends React
trait React extends Object {
//
}
という奇妙な記述が出てきたが、きっとtrait React
をobject 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 やパフォーマンスに取り組んでいるエディタで開くといい。
$c_Ltodomvc_Main$.prototype.main__V = (function() {
$g["React"]["render"](this.router$1, $g["document"]["getElementsByClassName"]("todoapp")[0])
});
およそ 12,500 行目付近にこのような出力がある。$g
はwindow
、そしてそこにwindow.React
となる形で$g["React"]
となっている。Scala.js コンパイル後の JS は大半の変数名が名前空間付きなので非常に長いが、長いだけで JS としては読めるレベルである。
この React は格納済みだが、Browserify
もnpm i
もしていないのにどこから?と調べてみたら、先の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 ログにも次のような表示がみられる。
[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
だ。
非常に奇妙なのが、このソースに現れるrender
だ。
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 側に実装があった。
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にある。
@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
だろう。
今回はここまで
Scala と出力 JS 読みながら、実際に動かしながら、それぞれ見ていった。気になったところは順次潰していったが、まだまだ興味は尽きない。implicit
やcase class
などは今後に持ち越す。
最後に実際に動いているものを。
「ああ…」としか言えん。
以上。
Discussion