Использование кода JavaScript из Kotlin
Kotlin был первоначально разработан для лёгкой работы с платформой Java: он воспринимает классы Java как классы Kotlin, а Java воспринимает классы Kotlin как классы Java.
Однако JavaScript — это язык с динамической типизацией, что означает, что он не проверяет типы во время компиляции. Вы можете свободно взаимодействовать с JavaScript из Kotlin с помощью динамических типов. Если вы хотите использовать весь потенциал системы типов 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.Они не могут быть переданы как обобщённые аргументы типа.
Они не могут быть использованы в выражениях литералов класса (таких как
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