Использование JavaScript-кода из Kotlin
Kotlin изначально был разработан для лёгкой работы с платформой Java: он рассматривает классы Java как классы Kotlin, а Java видит классы Kotlin как классы Java.
Однако JavaScript — язык с динамической типизацией, что означает, что проверка типов не выполняется на этапе компиляции. Вы можете свободно взаимодействовать с JavaScript из Kotlin с помощью типов dynamic. Если вы хотите использовать весь потенциал системы типов Kotlin, вы можете создать внешние объявления для JavaScript-библиотек, которые будут понятны компилятору Kotlin и связанным инструментам.
Встраиваемый 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).-
asприведения к внешним интерфейсам всегда выполняются успешно. Приведение к внешним интерфейсам производит предупреждение о времени компиляции «Непроверенное приведение к внешнему интерфейсу». Предупреждение можно подавить с помощью аннотации@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–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/js-interop.html