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

Kotlin + GradleでReactアプリを作る

RLook

この記事は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

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

この記事をシェア

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

関連する記事

2019年9月24日
夏のブログリレーエンディング
HF
2019年9月23日
ヒダッカソンに参加しました
oribe
2019年9月22日
ベクターレイヤーを使おう
Sigma1023
2019年9月21日
5,000円の液タブ(笑)を買ってみた。
Hinaruhi
2019年9月19日
定理証明で遊ぼう(しなさい)
stretchybox
2019年9月17日
コンピュータの旅をしよう
yasu

活動の紹介

カテゴリ

タグ