Kotlin/JSで作ったReact HooksをTypeScriptから使う


概要

Kotlin/JSでReactのカスタムフックを作成し、TypeScript用の型定義ファイルを生成します。
とりあえずTypeScriptから使えた程度の出来でまだ詳細は把握しきれておらず、実用性は皆無です。
Kotlin側のコードはKotlin/JSで直接Reactを使う場合でも同じく有効です。

下記の記事から分かる通り現時点で(2021/08/03)IRバックエンドはalphaなので、慎重に利用してください。
https://kotlinlang.org/docs/components-stability.html

Kotlin/JSを使う

Kotlin/JSのコードから型定義ファイル(.d.ts)を生成するためには、@JsExportを適用する必要があります。
しかし、プリミティブしかJavaScriptから取り扱うことができないため、いくら型定義ファイルを生成しても、JavaScriptから高度なライブラリを操作することはかなり厳しいです。

そこで、Ktolin/JSでラップすることで間接的に表現することができます。
本記事ではその例としてReact HooksとKotlin coroutinesの組み合わせを挙げています。

コード

Kotlin

build.gradle.kts
plugins {
    kotlin("js") version "1.5.30-M1"
}

group = "com.uramnoil"
version = "0.1.0"

repositories {
    mavenCentral()
}

val kotlinVersion = "1.5.21"
val reactVersion = "17.0.2"

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
    implementation("org.jetbrains.kotlin-wrappers:kotlin-react:17.0.2-pre.224-kotlin-1.5.21")
    implementation(peerNpm("react", reactVersion))}

kotlin {
    js(IR) {
        binaries.library()
        browser()
    }
}
hooks.kt
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import react.useState

val flow = MutableStateFlow(0)

@JsExport
fun useHoge(): String {
    var hoge by useState(flow.value)

    useEffectOnce {
        GlobalScope.launch { // NOTE: Dangerous API
            flow.collect {
                hoge = it
            }
        }
    }

    return hoge.toString()
}

// Flow操作用
@JsExport
fun increment() {
    flow.value++
}

TypeScript

package.json
{
    "dependencies": {
        "project-root-name": "link:/folder/to/ProjectRoot/build/js/packages/project-root-name"
    }
}
App.tsx
import "./App.css";
import { increment, useHoge } from "kotlin-react-library-sample";

function App() {
  const hoge = useHoge();

  return (
    <div className="App">
      {hoge}
      <button onClick={() => increment()}>hoge</button>
    </div>
  );
}

export default App;

サンプル

普通に動きます。

まとめ

ViewModelを扱うReact Hooksを作っておけば、Kotlin/Multiplatformでロジックを共通化していてもTypeScriptでフロントエンドを作れるようになります。

もう少し調査してみて記事を書いてみたいと思います。