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

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

この記事をシェア

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

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年9月8日
五度圏⊃自然音階って…コト!?
kotoki_bis icon kotoki_bis
2021年9月3日
部活青春系エロゲで涙腺崩壊した話
mera icon mera
2021年8月30日
【夏休み自由研究】実例で学ぶ画像処理【Python】
d_etteiu8383 icon d_etteiu8383
2021年8月29日
エロゲソングを聞け!!!
onzw icon onzw
2021年8月20日
おお我らがモニターヘッドホンは音の好みで選ぶな高校
liquid1224 icon liquid1224
記事一覧 タグ一覧 Google アナリティクスについて