Использование 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).-
Преобразования
asк внешним интерфейсам всегда успешны. Преобразование к внешним интерфейсам генерирует предупреждение о времени компиляции «Непроверенное преобразование к внешнему интерфейсу». Предупреждение можно подавить с помощью аннотации@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE").IntelliJ IDEA также может автоматически генерировать аннотацию
@Suppress. Откройте меню намерений с помощью значка лампочки или Alt-Enter и нажмите на стрелку рядом с проверкой «Непроверенное преобразование к внешнему интерфейсу». Здесь можно выбрать область подавления, и ваша IDE добавит аннотацию в ваш файл соответствующим образом.
Преобразования типов
В дополнение к оператору "unsafe" преобразования типа 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