Kotlin/JS TIPS - JavaScript ライブラリへの型情報の付与
概要
JavaScript ライブラリを使用する際に、型情報を付与する方法について説明します。 前回の記事で JavaScript ライブラリの使用方法を説明しましたが、 静的型付けによる恩恵を得ずに JavaScript の柔軟性を重視した使用方法としていました。 型情報を付与することによって、以下の恩恵を得られる様にしていきます。
ライブラリ開発者の手間を増やして、ライブラリ利用者の手間を減らしていきます。
前回の記事:
目次
確認環境
- IntelliJ IDEA Community 2017.2
- Kotlin 1.1.3-2
- Gradle 3.5
- Groovy 1.4.10
参考情報
- kotlin.js - Kotlin Programming Language
- Kotlin/JS 標準ライブラリ
- Calling JavaScript from Kotlin - Kotlin Programming Language
external interface
の使い方
解説
json 関数と Json インターフェース
JavaScript では {}
によってオブジェクト生成を行う。
Kotlin/JS では json 関数によってオブジェクトを生成できる。
生成されたオブジェクトは Json インターフェース を実装したオブジェクトであり、
[]
による操作を行える。
以下は SampleClient.kt
の一部である。
fun run() { val option = json() option["el"] = "#example" option["data"] = json("message" to "Hello World") }
生成される JavaScript は以下の様になっている。
function run() { var option = json([]); option['el'] = '#example'; option['data'] = json([to('message', 'Hello World')]); }
json 関数のソースコード (Kotlin) は以下になっており、js("({})")
でオブジェクトを生成していることがわかる。
public fun json(vararg pairs: Pair<String, Any?>): Json { val res: dynamic = js("({})") for ((name, value) in pairs) { res[name] = value } return res }
また、Json インターフェースのソースコード (Kotlin) は以下になっている。
public external interface Json { operator fun get(propertyName: String): Any? operator fun set(propertyName: String, value: Any?): Unit }
external
を使うことで JavaScript のコードをそのまま使用することができる。
get/set
を定義することで []
による参照/代入を可能にしている。
型情報の付与
json 関数の仕組みを応用して以下のとおりに記述することで、Json コンストラクタに見せかけることができる。
fun Json(): Json = js("({})") fun run() { val option = Json() option["el"] = "#example" }
さらに、external
を使うことで JavaScript のコードをそのまま使用できる性質を利用して、
[]
を使わずにオブジェクトに値を設定できる様にする。
例えば、以下の様に。
fun <T : Json> Json(): T = js("({})") external interface VueOption : Json { var el: Any var data: Json } external interface Model : Json { var message: String } fun run() { val option = Json<VueOption>() option.el = "#example" option.data = Json<Model>().apply { message = "Hello World" } option["computed"] = Json() // VueOption は Json インターフェースを実装しているので [] を使える }
生成された JavaScript は以下のとおり。
external
を使用した部分は簡潔なコードとなっている。
コード上に VueOption
や Model
インターフェースは現れない。
function Json() { return {}; } function run() { var option = Json(); option.el = '#example'; var $receiver = Json(); $receiver.message = 'Hello World'; option.data = $receiver; option['computed'] = Json(); }
さらに、初期化処理を行う関数を引数 (init
) にとる Json
関数を追加することで、
()
や apply
を省略できる。
fun <T : Json> Json(init: T.() -> Unit): T = Json<T>().apply(init) fun run() { val option = Json<VueOption> { el = "#example" data = Json<Model> { message = "Hello World" } this["computed"] = Json() }
external interface
を使うことで型情報を付与し、
補完機能の恩恵を得えつつ、
誤った型の値を代入する危険性を減らすことができる。
Vue.js に適用する
さらに、Vue
関数を SampleClient.kt
に追加すると、Vue の初期化が簡潔になる。
@JsNonModule @JsModule("vue") external class Vue(option: VueOption) fun <T : Json> Json(): T = js("({})") fun <T : Json> Json(init: T.() -> Unit): T = Json<T>().apply(init) external interface VueOption : Json { var el: Any var data: Json } external interface Model : Json { var message: String } fun Vue(init: VueOption.() -> Unit): Vue = Vue(Json<VueOption>().apply(init)) fun run() { val vm: dynamic = Vue { el = "#example" data = Json<Model> { message = "Hello World" } } vm.message = "Hello Kotlin World" }
前回の記事と比べると随分と簡潔になったと思う。
しかし、検討すべき課題は残っている。
補完機能の恩恵は受けられるようになったが、型安全という面では十分とは言えない。
上記の例で言えば、el
は String
もしくは HTMLElement
であるため、Any
にしている。
また、vm.message
を参照するために dynamic
にしている。
JavaScript の動的な側面を扱う方法については検討が必要である。