feature image

2019年9月20日 | ブログ記事

Kotlin + GradleでReactアプリを作る

この記事はtraP夏のブログリレー 9月20日の記事です。

こんにちは, RLookと申します。
夏休みも終りが近いですが, 夏休みどう過ごされましたか?
ぼくはだいたいKotlinを書いていたような気がします。KotlinFest 2019にも行ってきたので今年の夏はKotlinまみれでした。
そんな夏を締めくくるべく, 今日はKotlin + GradleでのReactアプリの作り方について簡単に説明します。

先行例

Kotlin + GradleでReactアプリを作った例としては, 次が一番いいのではないでしょうか。

rivasdiaz/react-tutorial-kotlin: An implementation of the React Tutorial using Kotlin

Playing with Kotlin React official wrapper - Ramon Rivas - Medium

実装の内容としては, Reactの公式のチュートリアルとなっています。
ただし, これはKotlin 1.1時代のものですから, まずはKotlin 1.3に上げるなどライブラリの更新と, Gradle Kotlin DSLを導入します。

その前にこの例で使われているGradleプラグインについてもさらっと見ておきます。

Kotlin2JS

その名の通りKotlinをJavaScriptへとトランスパイルするために必要なプラグインです。
本当はorg.jetbrains.kotlin.jsを使ってみたかったのですが, 後述するkotlin-frontend-pluginとの競合が発生していたため, 今回はこちらで済ませます。

kotlin-frontend-plugin

Kotlin/kotlin-frontend-plugin: Gradle Kotlin (http://kotlinlang.org) plugin for frontend development

README.mdには,

The plugin provides an easy way to gather Maven and npm dependencies, pack bundles (via webpack) and test a frontend application using Karma. By default the plugin generates all required configs for webpack, karma and manages the corresponding daemons.

とあります。すなわちnpmの依存関係をまとめることができ, かつWebpack等を用いてbundle化するのを助けてくれるプラグインのようです。

また, Reactのアプリを開発する上で次のライブラリが重要となります。

Kotlin Wrappers

JetBrains/kotlin-wrappers: Kotlin wrappers for popular JavaScript libraries

このライブラリは名前の通りReactのラッパーです。README.mdに書いてあるとおり, React以外にも, ReduxやReact-Routerなどもあります。

build.gradle.ktsの記述

さて先述したように, まずはgroovyで書かれているbuild.gradleを, Kotlin DSLを用いて書き直します。それに併せて, 各ライブラリを更新します。
次に変更例を示します。

import org.jetbrains.kotlin.gradle.frontend.webpack.WebPackExtension
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile

val reactVersion = "16.9.0"
val reactWrapperVersion = "16.9.0-pre.82-kotlin-1.3.41"

buildscript {
    val kotlinVersion by extra("1.3.50")
    val kotlinFrontendVersion by extra("0.0.45")

    repositories {
        mavenCentral()
        jcenter()
        maven(url="https://dl.bintray.com/kotlin/kotlin-eap")
    }

    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
        classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:$kotlinFrontendVersion")
    }
}

plugins {
    id("kotlin2js") version "1.3.50"
    id("org.jetbrains.kotlin.frontend") version "0.0.45"
}

repositories {
    mavenCentral()
    jcenter()
    maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")
}

dependencies {
    implementation(kotlin("stdlib-js"))
    implementation("org.jetbrains:kotlin-react:$reactWrapperVersion")
    implementation("org.jetbrains:kotlin-react-dom:$reactWrapperVersion")
}

kotlinFrontend {
    downloadNodeJsVersion = "latest"

    npm {
        dependency("react")
        dependency("react-dom")
    }

    bundle<WebPackExtension>("webpack") {
        this as WebPackExtension
        bundleName = "tictactoe"
        contentPath = file("src/main/web")
    }
}

gradle.projectsEvaluated {
    tasks.withType(Kotlin2JsCompile::class) {
        kotlinOptions {
            moduleKind = "commonjs"
        }
    }
}

ポイントとしては, メソッドwebpackBundleが存在しないため, 代わりにbundleを使う必要があることです。ただし, このときラムダ式内のthisWebPackExtensionのスーパークラスであるBundleConfigとなっているため, キャストを挟む必要があります。今回はラムダ式内1行目にて一見無駄なキャストを挟むことで, スマートキャストを働かせています。

このように変更してから, Gradleタスクrunを実行すると, 次のエラーが発生します。

FAILURE: Build failed with an exception.

* What went wrong:
A problem was found with the configuration of task ':webpack-config'.
> Directory '$PROJECT_DIR\react-tutorial-kotlin\webpack.config.d' specified for property '$1' does not exist.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

ここでは, プロジェクトのルートにwebpack.config.dという名のディレクトリを追加するとエラーを解消することができます。
この後もう一度runをすると, ようやく開発用のサーバが立ち上がります。
今回はReactチュートリアルではおなじみのマルバツゲームが立ち上がるはずです。

chrome_2019-09-20_19-44-52

KotlinでのReactアプリの実装

簡単にJavaScriptによる実装と比較しながら, KotlinでのReactの書き方を確認しましょう。

コンポーネントの宣言

JSでの実装は通常次のようになります。

class Hoge extends React.Component {
    render() {
        return (...)
    }
    
    foo() {
        this.setState({
            x: 30
        })
    }
}

一方でkotlinでは次のようになります。

class Hoge: RComponent<Props, Status>() {
    override fun RBuilder.render() {
        ...
    }
    
    private fun foo() {
        setState {
            x = 30
        }
    }
}

interface Props: RProps { ... }

interface Status: RState { ... }

Kotlinによる実装では, propsとstateがどのインターフェースであるかを宣言する必要があります。これはTypeScriptによる実装に近いですが, 違いとしてはpropsとして使うインターフェースはRPropsを実装する必要があり, 同様にstateとして使うインターフェースはRStateを実装する必要があります。

propsやstateを使わない場合にもこれらを指定する必要があり, この場合先行例ではそれぞれRPropsRStateをそのまま指定していますが, Nothingで代えることもできます。

またrenderメソッドに関しては, KotlinではRBuilderの拡張関数として宣言されています。

setStateメソッドに関しては, ラムダ式中で各フィールドに代入するように書くことができ, Kotlinとして自然に書くことができます。

コンポーネントの呼び出し

例えばGamerenderでは次のように書かれています。

<Board
    squares={current.squares}
    onClick={i => this.handleClick(i)}
/>

一方で, KotlinではJSXのようにはいかないため, 次のように書く必要があります。

class Hoge: RComponent<...> {
    override fun RBuilder.render() {
        board(current.squares) {
            handleClick(it)
        }
    }
}

fun RBuilder.board(squares: Array<String?>, onClickFunction: (Int) -> Unit) = child(Board::class) {
    attrs.squares = squares
    attrs.onClickFunction = onClickFunction
}

このようにコンポーネントを用意するたびに, 適当な拡張関数をもってpropsが渡るように書く必要が生じます。

エントリポイント

チュートリアル中では次のように書かれています。

ReactDOM.render(<Game />, document.getElementById("root"));

一方でKotlinでの実装は次のようになります。

fun main() {
    window.onload = {
        document.getElementById("root")?.let {
            render(it) { child(Game::class) {} }
        }
    }
}

このようにしてReactのアプリを構築していきます。

おわりに

簡単にではありましたが, KotlinでReactのアプリを作る手順を示しました。
現状ではまだまだ難しい部分が多いですが, Kotlin-WrappersはJetBrainsから(Team Projectではあるが)出ていますので, これからの発展が望まれるところです。

明日の担当はHinaruhiさんです。お楽しみに。

RLook icon
この記事を書いた人
RLook

アイコンはどこかの生徒会書記です。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2019年8月27日
曲が...曲が出来ていくぞ!!!!
kashiwade icon kashiwade
2019年8月26日
ゲームを作ろう!
Kanagu icon Kanagu
2018年9月15日
論文の紹介【Universal Transformers】
hukuda222 icon hukuda222
2020年9月17日
鯨飼いになろう講習会をやった話
mazrean icon mazrean
2020年9月5日
「音割れX-File」で学ぶ!色んなディストーション【夏のブログリレー5日目】
liquid1224 icon liquid1224
2019年9月15日
YouTubeを見よう!
Silviase icon Silviase
記事一覧 タグ一覧 Google アナリティクスについて