scalajs-reactの仮想DOMに見る、Scala.js的ライブラリのアプローチ

## はじめに

[scalajs-react](https://github.com/japgolly/scalajs-react)は、[Scala.js](https://www.scala-js.org/)で[React](https://ja.reactjs.org/)を使うためのライブラリです。
この記事では、scalajs-reactの[仮想DOM](https://github.com/japgolly/scalajs-react/blob/master/doc/VDOM.md)の使い方を紹介しつつ、ScalaとJSの世界を繋ぐアプローチを覗き見してみようと思います。

### 対象読者

- Scala.jsに興味がある方。
- Reactや仮想DOMが好きな方。

### 前提条件

- Reactそのものの思想や仕組みはある程度把握しているものとします。
- Scala.jsの基本的な使い方を把握しているものとします。

### 準備

プロジェクトのセットアップは[公式ドキュメント](https://github.com/japgolly/scalajs-react/blob/master/doc/USAGE.md)を参照してください。

仮想DOMを記述するファイルではタグのセットをインポートしますが、プレフィックス付きで利用することが推奨されています。（プレフィックスなしにもできますが、名前の衝突の危険性があります）

```scala
<.div(^.id := "my-id") // <div id="my-id">
```

タグはHTMLとSVGが用意されています。

`import japgolly.scalajs.react.vdom.html_<^._`
`import japgolly.scalajs.react.vdom.svg_<^._`

## 使ってみる

詳細は[ドキュメント](https://github.com/japgolly/scalajs-react/blob/master/doc/VDOM.md)を参照してください。

ここでは最低限の使い方をざっくりと紹介します。

手前味噌ですが、[私の発表資料](https://speakerdeck.com/yuiwai/frontend-with-scalajs-react)もよければどうぞ。

### 要素

`<.タグ名`のシンタックスで要素を表現します。

要素は入れ子にすることができます。

```scala
<.ul(
  <.li("a"),
  <.li("b"),
  <.li("c")
)
```

要素は、型としては`VdomElement`として扱われます。詳細についてはのちほど見たいと思います。

### 属性

要素の属性は`^.属性名`のシンタックスで表現されます。

```scala
<.div(
  ^.id := "my-id"
)
```

先ほどは要素の入れ子を表現しましたが、属性も子要素と同じように並べて書くことができます。
仕組みはのちほど解説しますが、`:=`が`TagMod`を返すメソッドになっているのがミソです。

### イベントハンドリング

属性として表現しますが、値の設定のみの場合とちょっと異なります。

`-->`や`==>`を使います。

```scala
<.button(
  ^.onClick --> Callback.alert("clicked!")
)
```

今回のテーマからは逸れるので、イベントハンドリングについてはこれ以上は触れません。

### スタイル

属性にスタイルを指定してもよしなに処理してくれるようになっています。
例えば`margin`はスタイルですが、属性と同じシンタックスで指定できます。

```scala
<.div(^.margin := "3px")
```

内部的には`Style`という専用の型があり、`TagMod`として処理されています。

また、[ScalaCSS](https://github.com/japgolly/scalacss)はscalajs-reactの作者が作ったライブラリで互換性があり、scalajs-reactの仮想DOM上でスタイルをシンプルに結合できます。
詳細は[ドキュメント](https://japgolly.github.io/scalacss/book/ext/react.html)を参照してください。


## 内部を掘り下げてみる

scalajs-reactの仮想DOMは、Scala上の型と、JS上でReactが扱う型との間の橋渡しをします。
`japgolly.scalajs.react.raw`パッケージ以下にあるのが、JS側のReactの型に対するファサードです。これらの型に対して、Scala側で扱う型をうまくマッピングして相互運用することになります。

では、Scala側の仮想DOMを表現する型はどのようなものがあるのでしょうか？
主なものを見ていきます。

### TagMod

scalajs-reactの仮想DOM表現を支える重要な基底型です。次項以降見ていく仮想DOMの構成要素がこの`TagMod`から派生していることにより、柔軟な組み合わせが可能となっています。

`TagMod`は`applyTo`という抽象メソッドを持った`trait`です。

```scala
def applyTo(b: Builder): Unit
```

`Builder`は仮想DOMの操作を抽象化した`trait`で、Scala側からJS側のReactの仮想DOMを構築する、などのケースで使用されます。

下記は`Builder`の一部の抜粋です。詳細は割愛しますが、雰囲気は感じていただけるかと思います。

いずれのメソッドも返り値の型は`Unit`です。つまり、`Builder`は、内部に可変な状態を持ったオブジェクトです。`Vdom`の構築処理は高頻度で発生し、高いパフォーマンスが求められるので、こうするのがベストなのでしょう。

```scala
trait Builder {
  val addAttr        : (String, js.Any) => Unit
  val addClassName   : js.Any           => Unit
  val addStyle       : (String, js.Any) => Unit
  val addStylesObject: js.Object        => Unit
  val appendChild    : RawChild         => Unit
  val setKey         : js.Any           => Unit
}
```

`TagMod`の責務は`applyTo`メソッドの具象実装を介して、**`Builder`を使用して仮想DOMを構築すること**になります。

派生型の詳細は次項以降で順に見ていきますので、ここでは`TagMod`を利用する（受け取ったり、返したり、コレクションに格納したり）ことにより複雑な入れ子構造である仮想DOMのデータ構造を柔軟に取り扱うことが出来るようになる、ということだけ述べるに留めておきます。


### TagOf

`TagOf`は、タグセット(HTML/SVG)に対応したタグを表す型です。後述する`VdomElement`から派生した`具象クラス`となります（`VdomElement`は`trait`です）

```scala
class TagOf[+N <: TopNode] private[vdom](final val tag: String,
                                         final protected val modifiers: List[Seq[TagMod]],
                                         final val namespace: Namespace) extends VdomElement
```

`modifiers`の型が`List`の中に`Seq`が入っていますが、この理由は`apply`メソッドを見ると分かります。

```scala
  def apply(xs: TagMod*): TagOf[N] =
    copy(modifiers = xs :: modifiers)
```

つまり、任意の可変長`TagMod`を受け付けてイミュータブルに付け足せるようにしているわけですね。

実装を追うとわかるのですが、付け足した`TagMod`は最終的に古いものから順に評価されます(`VdomElement`の項で触れます)。これにより、最後に付け足した`TagMod`で既存の処理を上書きできることになります（というか、それができないと付け足せる意味がないですよね）


次に、HTMLのタグに対応する`TagOf`を生成する（ファクトリとして振る舞う）`HtmlTagOf`クラスを見てみましょう。

```scala
final case class HtmlTagOf[+N <: HtmlTopNode](name: String) extends AnyVal {
  def apply(xs: TagMod*): TagOf[N] =
    new TagOf(name, xs :: Nil, Namespace.Html)
}
```

コンストラクタ引数の`name`はHTMLタグ名(`div`や`span`や`input`など）です。
このクラスは、可変長引数`xs: TagMod*`を取る`apply`メソッドを持っています。

これが、scalajs-reactの仮想DOMで要素を生成する`<.タグ名(...)`の正体です。

`<.div`が何を表しているのか見てみましょう。

```scala
final def div = HtmlTagOf[*.Div]("div") // *はorg.scalajs.dom.htmlのエイリアス
```

タグ名`"div"`を引数に`HtmlTagOf`のインスタンスを生成していますね。
こうして得られた`HtmlTagOf[html.Div]`の`apply`が`TagMod*`を引数に取り、`TagOf`を返しています。


### VdomNode

`TagMod`から直接派生した`trait`で、JS側のReactのNodeを返す`def rawNode`を持っています。

`applyTo`を実装していますが、`Builder`の`appendChild`に`rawNode`を引き渡すだけのシンプルなものです。

```scala
trait VdomNode extends TagMod {
  def rawNode: Raw.React.Node

  override def applyTo(b: Builder): Unit =
    b.appendChild(rawNode)
}
```

`Raw.React.Node`が何者かというと

```scala
type Node = ChildrenArray[Empty | String | JsNumber | Element]
```

となっています。

ここで、Scala.jsの`union type`が出てきました。
`A | B | C`は、`A`、`B`、`C`のどれか、を表す型となります。
(TypeScriptではおなじみかと思います。[Scalaも3系からは導入される予定](https://dotty.epfl.ch/docs/reference/new-types/union-types.html)です)

`ChildrenArray`もタイプエイリアスですので、こちらも見てみましょう。

```scala
type ChildrenArray[A] = A | recursiveTypeAliases.ChildrenArray[A]
```

`A`か、`recursiveTypeAliases.ChildrenArray[A]`だそうです。

後者はさらに

```scala
type ChildrenArray[A] = js.Array[raw.ChildrenArray[A]]
```

となり、つまるところ、この型は再帰的に展開される配列となります。

`A`は、`Empty`か、`String`か、`JsNumber`か、`Element`でした。

なので、`Raw.React.Node`は、**空か、文字列か、数値か、Element**の、**単体か、あるいは再帰的な配列**となるかと思います。

これで、入れ子で可変長で、文字列や、数値などのプリミティブも持てる`Node`の柔軟なデータ型の表現が実現できています。

### VdomElement

前述の`VdomNode`から派生した`trait`です。

以下、一部抜粋します。

```scala
trait VdomElement extends VdomNode {

  override def rawNode = rawElement

  def rawElement: Raw.React.Element
}
```

`VdomNode`で宣言された抽象メソッド`rawNode`を実装し、`Raw.React.Element`を返す`def rawElement`を新たに抽象メソッドとして宣言しています。
この`rawElement`はJS側のReactの`Element`となります。

`rawElement`の具象実装がどこにあるのか、というと、すでに登場した`TagOf`が`VdomElement`から派生した具象実装クラスだったので、この中にあるはずです。
ということで、見てみましょう。

```scala
  override lazy val rawElement: Raw.React.Element = {
    val b = new Builder.ToRawReactElement()

    val arr = new js.Array[Seq[TagMod]]
    var current = modifiers
    while (current != Nil) {
      arr.push(current.head)
      current = current.tail
    }
    var j = arr.length
    while (j > 0) {
      j -= 1
      arr(j).foreach(_.applyTo(b))
    }

    b.render(tag)
  }

```

`lazy val`になっているのは、`TagOf`がイミュータブルなので、一度計算すれば結果は変わらないからです。
最初に`Builder`のインスタンスを作成しています。この`Builder`を使い、`TagOf`をJS側の`Element`に変換します。

`modifiers`の型は`List[Seq[TagMod]]`でした。
これを一度、`while`ループで、`js.Array`に詰め替えています。
この後のループでインデックス指定によるランダムアクセスを行なっていますが、`List`のままだとそこが遅い、ということなのでしょうか。加えて、Scalaのデータ構造を使うとそれだけでオーバーヘッドがあるので、パフォーマンスを気にする箇所ではJS側のデータ構造の方が良い、というのもあると思います。
ちなみに、`List`を使っているのは、`TagOf`の`apply`で`modifiers`を追加する処理が先頭への要素の追加で、これが`List`であれば`O(1)`で実行できて、かつイミュータブルである、というところなのかと思います。
（この辺、実際に自分でベンチマークを取ったわけではないので、あくまで推測に過ぎないです。ただ、こう書いてあるということは、これが現実的に早いということだと理解しています）

蛇足ですが、パフォーマンスに関して。
大量の（あるいは高頻度で実行されるような）ループ処理の場合、限定されたスコープ内であれば、可変な変数を使ってチューニングすることも検討するのがいいと思います。イミュータブルなデータ構造は状態の変更を伴うメソッド呼び出しの度に新しいデータ構造を作って返します。一度の処理のオーバーヘッドは小さくても、それが大量のループの中で実行されると、無視できない計算量になってきます。
（不安があれば、データ量を見積もって計測しましょう）

...ちょっと話題がそれましたが、戻ります。

最終的に詰め替えた`modifiers`をループで回して順に`Builder`を渡して`applyTo`していきます。
この時、indexを後ろから辿っていますが、この処理で、後に追加した`TagMod`が後に評価されるようにしています。

全てを終えたら、`Builder.render`メソッドで結果を`Element`として返して終了です。

### VdomArray

前述の`VdomNode`から派生した`final class`です。
一部抜粋します。

```scala
final class VdomArray(val rawArray: js.Array[Raw.React.Node]) extends VdomNode {
  override def rawNode = rawArray.asInstanceOf[Raw.React.Node]
}
```

`Node`自体は再帰的に配列として持てる構造だったので、`rawArray`は`Node`にキャスト出来るということですね。

さて、この`VdomArray`には重要な特徴があります。それは、**破壊的な`Node`の追加が可能**というものです。

`VdomArray`に生えている2つのメソッドを見てみます。

```scala
  def +=(n: VdomNode): this.type = {
    rawArray push n.rawNode
    this
  }

  def ++=[A](as: TraversableOnce[A])(implicit f: A => VdomNode): this.type = {
    for (a <- as)
      rawArray push f(a).rawNode
    this
  }
```

`rawArray`に直接`Node`を追加して、自分自身を返していますね。おそらくはパフォーマンス的な要件で必要になると思われますが、外に漏らすと不変条件をぶっ壊す可能性があるので、要注意です（というか、通常の用途では使うことはないのかと思います）


### VdomAttr/Attr/Style

（ここにはイベントも含まれるのですが、今回は除外します）

要素に対する属性値を表しています。
前述したように、純粋な属性値の他にスタイルも同様のシンタックスで扱えるように工夫されています。

属性名と値のペアは以下のように生成できます。

```scala
^.id := "my-id"
```

これが`TagMod`を返すことは先に軽く触れました。これをもう少し詳しく見ていきたいと思います。

`^.属性名`の部分の意味合いは、`<.要素名`と似たものです。この呼び出しで、名前に該当する属性（の派生であるスタイルなど）のインスタンスが返ります。そして、そのインスタンスには`:=`メソッドが生えています。

`:=`メソッドは、抽象クラスである`Attr[-U]`の中で、以下のように宣言されています。

```scala
  def :=[A](a: A)(implicit t: ValueType[A, U]): TagMod
```

`implicit`な引数として受け取っている`ValueType[A, U]`は、属性が受け取ることの出来る値(`:=`メソッドの右辺)の型に対する制約をかけています。

`href`の例(この属性は文字列の値を要求しています)を見てみます。

```scala
  final def href = VdomAttr[String]("href")
```

`VdomAttr`は`Attr`のコンパニオンオブジェクトへのエイリアスとして機能していて、結果として`href`の型は`Attr[String]`となります。

この場合、`def :=[A]`メソッドが要求する`implicit`なインスタンスは`ValueType[A, String]`となります。
`ImplicitsForVdomAttr`の`trait`の中で、各種のインスタンスが定義されていますが、その中に`ValueType[String, String]`も含まれるため、このメソッドに文字列を渡せば無事にコンパイルが通ります。

属性の種類によっては（例えばスタイルなど）この後の処理が若干違ってくるのですが、ここでは汎用の属性の処理だけ見ることにします。
`:=`メソッドは`ValueType[A, U]`の`apply`メソッドを呼び出します。
このメソッドは以下のようになっています。

```scala
    def apply(name: String, value: A): TagMod =
      TagMod.fn(b => fn(b.addAttr(name, _), value))
```

`TagMod.fn(f: Builder => Unit)`を利用することで、**要素に対して属性を足す**という処理を持った`TagMod`として定義されています。
ここで指定した`f`は、`applyTo`の中で呼び出されます。属性は、適用対象の要素のmodifiersとして保持されているので、`applyTo`に引き渡される`Builder`は、適用対象の要素のコンテキストを持っています。これで、ちゃんと指定した要素に対する属性として機能するようになっているわけです。(適用処理は`VdomElement`の項を参照してください)


## TagModのパワー

`TagMod`の抽象化のおかげで、様々な単位でパーツを切り出して組み合わせたり再利用することが容易になります。

例えば、`TagOf`は、`apply(xs: TagMod*)`メソッドを使って任意の`TagMod`を後から付け加えることができることができました。これは、`TagOf`を生成する処理と、`TagMod`を生成する処理を分離するなど、柔軟な構成を可能にします。

Viewのヘルパ関数を作る時にも`TagMod`による抽象化は重宝します。
汎用的な（例えばコンポーネント的なもの）処理に具象実装を引き渡す時、`TagMod`を返す関数を受け取ったりできます。
例えば、あるデータ型のコレクション（ここでは`List[Item]`とします）を受け取って、何かしらのレイアウトを適用するコンポーネントを作りたい時、各`Item`を受け取って、`TagMod`を返す関数を引き渡すことができます。
`TagMod`の代わりに`VdomElement`を返す関数でも十分ではあるのですが、`TagMod`の方が抽象度が高く、より汎用的なケースに対応出来ます。

最終的に大きな一つの仮想DOMを組み上げることには変わりはないのですが、それを合理的に小さな部品に分解出来るのが便利なのです。


## あとがき

scalajs-reactの仮想DOMまわりの処理について、少し追ってみました。

Scalaという静的な型付けの世界からJSという動的な型付けの世界へ橋渡しをするには、色々な工夫が必要ですが、その一端を垣間見ることができて非常に興味深かったです。
また、パフォーマンスを維持するためにはどんな実装が必要かという観点からも、Scalaコードの書き方に関するヒントがたくさんありました。

機会があれば、今回取り上げていない側面、コンポーネント周りであるとか、イベントハンドラ周りも掘り下げてみたいですね。