Spec-Zone.ru › Kotlin 1.4

Вызов 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")
    }
}

Есть некоторые ограничения:

  • Когда функция внешнего базового класса перегружена по сигнатуре, вы не можете переопределить ее в производном классе.
  • Вы не можете переопределить функцию с аргументами по умолчанию.
  • Не-внешние классы не могут быть расширены внешними классами.

external интерфейсы

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–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/js-interop.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API