10
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.

KotlinのReact用ライブラリの実装を読む

Last updated at Posted at 2017-12-22

概要

この記事では、KotlinでReactアプリを書くときに使うライブラリの実装についてみていきます。Reactについては詳しく説明しません。
実装を読む気持ちとしては、React用のKotlinDSLがどのように実装されているか気になる、Kotlinのコードを読んでKotlinに関する知見を深めるといったことがあります。

はじめに

はじめてKotlinでReactアプリを書く場合、create-react-kotlin-appというnodeモジュールを利用することで簡単にサンプルプロジェクトを作成できます。

kotlin-wrappersの中にある、kotlin-reactとkotlin-react-domというKotlinのライブラリを使うことで、Kotlinらしい書き方ができます。

以下は、create-react-kotlin-appで作成したサンプルプロジェクトのソースコードの抜粋です。(紙面の都合上、複数ファイルに書いてあるものを一箇所に書いています)

サンプルコード抜粋
fun main(args: Array<String>) {
    render(document.getElementById("root")) {    // -- (1)
        app()
    }
}

class App : RComponent<RProps, RState>() {
    override fun RBuilder.render() {
        div("App-header") {                      // -- (2)
            key = "header"
            logo()
            h2 {
                +"Welcome to React with Kotlin"  // -- (3)
            }
        }
        p("App-intro") {
            key = "intro"
            +"To get started, edit "
            code { +"app/App.kt" }
            +" and save to reload."
        }
        p("App-ticker") {
            key = "ticker"
            ticker()
        }
    }
}

fun RBuilder.app() = child(App::class) {}

JavascriptでReactアプリを実装する場合は、render関数内でJSXというHTMLのようなXMLを記述しますが、Kotlinの場合は、すべてKotlinのDSLで記述できていることが分かりますね。

このようなDSLで記述にするためにライブラリの中はどのような実装になっているのでしょうか?
この記事では、以下の三点について見ていきます。

(1) main関数内のrender関数は何をやってるの?
(2) div関数の中はどうなっているの?
(3) 文字列の前についている+ってなに?

ライブラリのソースを読む

登場人物

ソースを読む前に、よく出てくるクラスを列挙しておきます。

クラス名 役割
RBuilder ビルダー用の関数群をもつ。
RDOMBuilder React用DOMのビルダー。RBuilderを継承している。createするとReactElementが取れる。
ReactElement Reactの要素。external修飾子付き。

(1) main関数内のrender関数は何をやってるの?

はじめにrender関数です。

render関数
fun render(container: Element?, handler: RBuilder.() -> Unit) = render(buildElements(handler), container)

第一引数にJavaScriptのElementを、第二引数にRBuilderの関数を受け取ります。
受け取った引数を別のrender関数に渡しています。

buildElements関数は、elementを作って返却しています。

別のrender関数
@file:JsModule("react-dom")

external fun render(element: dynamic, container: Element?, callback: () -> Unit = definedExternally)

こちらのrender関数には、external修飾子がついています。
react-domというJavaScriptのモジュールで実装されていて、Kotlinでの実装は存在しないです。実行時は、JavaScriptのReactDOM.renderが呼ばれます。

render関数についてまとめると、elementを作って、ReactDOM.renderに渡しています。
ReactDOM.renderにはexternal修飾子がついていて、JavaScript側の実装で処理されます。

(2) div関数の中はどうなっているの?

続いてdivです。

RBuilder.div
inline fun RBuilder.div(classes: String? = null, block: RDOMBuilder<DIV>.() -> Unit): ReactElement = 
  tag(block) { DIV(attributesMapOf("class", classes), it) }

第一引数にクラス名を、第二引数にRDOMBuilderの関数を受け取り、ReactElementを返却します。
実装は、tag関数にRDOMBuilder.() -> Unit{ DIV(...) }を渡しています。

RBuilder.tag
inline fun <T : Tag> RBuilder.tag(block: RDOMBuilder<T>.() -> Unit, noinline factory: (TagConsumer<Unit>) -> T): ReactElement =
  child(RDOMBuilder(factory).apply { block() }.create())

tag関数ですが、RDOMBuilderにfactoryを渡して、block(RDOMBuilder.() -> Unit)を実行したものを、createして子要素として追加しています。
以下のコードの場合、"Hello"がh2の子要素になり、h2がdivの子要素となることが分かると思います。

サンプル
div("App-header") {
  h2 {  +"Hello" }
}

さて、まだ<div>というHTMLの要素が出てきてないですね。
RBuilder.divはどのように<div>になるのでしょうか。

RDOMBuilder.create
open fun create(): ReactElement = 
  React.createElement(attrs.tagName, props, childList.toTypedArray())

登場人物のところで、RDOMBuilderをcreateするとReactElementが取れると書きましたが、実装上は、上記のようにReact.createElementを呼ぶ実装になっています。
React.createElementはexternal修飾子がついた関数で、以下のような仕様です。

React.createElement(
 type,
 [props],
 [...children]
)
引用元: https://reactjs.org/docs/react-api.html#createelement

typeにはdivspanのようなタグ名が入るとあるので、attrs.tagNamedivが入っていると想像がつきます。そして、ReactJS側で<div>になるはずです。

kotlinx.html.DIV
open class DIV(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>)
  : HTMLTag("div", consumer, initialAttributes, null, false, false), HtmlBlockTag { } 

open class HTMLTag(
  override val tagName : String,
  override val consumer : TagConsumer<*>,
  initialAttributes : Map<String, String>,
  override val namespace : String? = null,
  override val inlineTag: Boolean,
  override val emptyTag: Boolean
) : Tag {
  override val attributes : DelegatingMap = DelegatingMap(initialAttributes, this) { consumer }

  override val attributesEntries: Collection<Map.Entry<String, String>>
  get() = attributes.immutableEntries
}

実際にコードを読むと、DIVというクラスが、HTMLTagのtagNameにdivを入れたクラスになっていて、RDOMBuilderにtagNameが渡っていることが分かります。

ここでは、div関数の実装について見てきました。
RBuilderの関数が、RDOMBuilder<T>.() -> Unitを引数に取るようになっていて、HTMLの構造をラムダでうまく表現できるようになっていました。

(3) 文字列の前についている+ってなに?

最後に小ネタですが、なぜ文字列の前に+をつけているのでしょうか。

RBuilder
operator fun String.unaryPlus() {
  childList.add(this)
}

operator overloadingによって、Stringの前に+をつけると、子要素として追加される実装になっています。
operator overloadingは全く使ったことがなかったですが、実際に使われているのを見ると書いてみたくなりますね。

まとめ

この記事では、KotlinのReact用wrapperの実装をみてきました。
ライブラリの実装を読むことで、「Kotlinでこんなこともできるんだ!」というような新たな発見があって面白いです。

Kotlinの仕様を読めば書いてあることばかりですが、実際にどういう場面で使われるのかが分かるとさらにKotlinらしいコードが書けるようになる手助けになることでしょう。

関連資料

10
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
10
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?