Использование JavaScript-кода из Kotlin
Kotlin изначально был разработан для лёгкой интеграции с платформой Java: он рассматривает Java-классы как Kotlin-классы, а Java — Kotlin-классы как Java-классы.
Однако JavaScript — язык с динамической типизацией, то есть он не проверяет типы во время компиляции. Вы можете свободно взаимодействовать с JavaScript из Kotlin с помощью типов dynamic. Если вы хотите использовать всю мощь системы типов Kotlin, вы можете создать внешние объявления для JavaScript-библиотек, которые будут понятны компилятору Kotlin и связанным инструментам.
Доступен также экспериментальный инструмент для автоматического создания внешних объявлений Kotlin для npm-зависимостей, предоставляющих определения типов (TypeScript / d.ts), называемый Dukat.
Встраивание JavaScript-кода
Вы можете встроить код JavaScript в свой Kotlin-код, используя функцию js(). Например:
fun jsTypeOf(o: Any): String {
return js("typeof o")
}
Поскольку параметр js парсится во время компиляции и транслируется в JavaScript-код «как есть», он должен быть строковой константой. Поэтому следующий код неверен:
fun jsTypeOf(o: Any): String {
return js(getTypeof() + " o") // error reported here
}
fun getTypeof() = "typeof"
Обратите внимание, что вызов js() возвращает результат типа dynamic, который не обеспечивает проверку типов на этапе компиляции.
Модификатор external
Чтобы сообщить Kotlin, что определённое объявление написано на чистом JavaScript, необходимо пометить его модификатором external. Когда компилятор видит такое объявление, он предполагает, что реализация соответствующего класса, функции или свойства предоставлена внешне (разработчиком или через npm-зависимость), и поэтому не пытается сгенерировать JavaScript-код из объявления. Вот почему объявления с модификатором external не могут иметь тело. Например:
external fun alert(message: Any?): Unit
external class Node {
val firstChild: Node
fun append(child: Node): Node
fun removeChild(child: Node): Node
// etc
}
external val window: Window
Обратите внимание, что модификатор external наследуется вложенными объявлениями. Поэтому в примере класса Node перед членами-функциями и свойствами нет модификатора external.
Модификатор external разрешён только для объявлений на уровне пакета. Нельзя объявить external член не-external класса.
Объявление статических членов класса
В JavaScript члены можно определять либо на прототипе, либо на самом классе:
function MyClass() { ... }
MyClass.sharedMember = function() { /* implementation */ };
MyClass.prototype.ownMember = function() { /* implementation */ };
В Kotlin такого синтаксиса нет. Однако в Kotlin есть companion объекты. Kotlin обрабатывает объекты-компаньоны для external классов особым образом: вместо ожидания объекта он предполагает, что члены объектов-компаньонов являются членами самого класса. MyClass из примера выше можно описать следующим образом:
external class MyClass {
companion object {
fun sharedMember()
}
fun ownMember()
}
Объявление необязательных параметров
Если вы пишете внешнее объявление для JavaScript-функции с необязательным параметром, используйте definedExternally. Это делегирует генерацию значений по умолчанию самой JavaScript-функции:
external fun myFunWithOptionalArgs(
x: Int,
y: String = definedExternally,
z: String = definedExternally
)
С этим внешним объявлением вы можете вызвать myFunWithOptionalArgs с одним обязательным аргументом и двумя необязательными, где значения по умолчанию вычисляются JavaScript-реализацией myFunWithOptionalArgs.
Расширение JavaScript-классов
Вы можете легко расширять JavaScript-классы, как если бы они были Kotlin-классами. Просто определите external open класс и расширьте его не-external классом. Например:
open external class Foo {
open fun run()
fun stop()
}
class Bar: Foo() {
override fun run() {
window.alert("Running!")
}
fun restart() {
window.alert("Restarting")
}
}
Есть некоторые ограничения:
Если функция внешнего базового класса перегружена по сигнатуре, вы не можете переопределить её в производном классе.
Вы не можете переопределить функцию с аргументами по умолчанию.
Не-внешние классы не могут быть расширены внешними классами.
Внешние интерфейсы
JavaScript не имеет понятия интерфейсов. Если функция ожидает, что её параметр поддерживает два метода foo и bar, вы просто передадите объект, который фактически имеет эти методы.
Вы можете использовать интерфейсы для выражения этой концепции в статически типизированном Kotlin:
external interface HasFooAndBar {
fun foo()
fun bar()
}
external fun myFunction(p: HasFooAndBar)
Типичный случай использования внешних интерфейсов — описание объектов настроек. Например:
external interface JQueryAjaxSettings {
var async: Boolean
var cache: Boolean
var complete: (JQueryXHR, String) -> Unit
// etc
}
fun JQueryAjaxSettings(): JQueryAjaxSettings = js("{}")
external class JQuery {
companion object {
fun get(settings: JQueryAjaxSettings): JQueryXHR
}
}
fun sendQuery() {
JQuery.get(JQueryAjaxSettings().apply {
complete = { (xhr, data) ->
window.alert("Request complete")
}
})
}
Внешние интерфейсы имеют некоторые ограничения:
Их нельзя использовать в правой части проверок
is.Их нельзя передавать в качестве аргументов типа reified.
Их нельзя использовать в выражениях литералов классов (таких как
I::class).-
Приведение к внешним интерфейсам всегда выполняется успешно. Приведение к внешним интерфейсам производит предупреждение о времени компиляции «Непроверенное приведение к внешнему интерфейсу». Предупреждение можно подавить с помощью аннотации
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE").IntelliJ IDEA также может автоматически сгенерировать аннотацию
@Suppress. Откройте меню намерений с помощью значка лампочки или Alt-Enter, и щелкните небольшую стрелку рядом с проверкой «Непроверенное приведение к внешнему интерфейсу». Здесь вы можете выбрать область подавления, и ваша IDE добавит аннотацию в ваш файл соответственно.
Приведения типов
В дополнение к оператору «небезопасного» приведения as, который выбрасывает ClassCastException в случае, если приведение невозможно, Kotlin/JS также предоставляет unsafeCast<T>(). При использовании unsafeCast, вообще никакой проверки типов не выполняется во время выполнения. Например, рассмотрите следующие два метода:
fun usingUnsafeCast(s: Any) = s.unsafeCast<String>() fun usingAsOperator(s: Any) = s as String
Они будут скомпилированы соответственно:
function usingUnsafeCast(s) {
return s;
}
function usingAsOperator(s) {
var tmp$;
return typeof (tmp$ = s) === 'string' ? tmp$ : throwCCE();
}
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/js-interop.html