Web/JS 互操作
Kotlin/JS 允许直接调用 JavaScript 代码,并将 Kotlin 代码编译为 JS 模块供前端使用。这为 Web 平台开发提供了强大的跨平台能力。
Kotlin 调用 JavaScript
kotlin
// jsMain
import kotlin.js.JSON
import kotlin.js.json
// 调用全局 JavaScript 函数
external fun alert(message: String)
external fun confirm(message: String): Boolean
fun showMessage() {
alert("Hello from Kotlin!")
val result = confirm("Continue?")
}
// 访问 window 对象
external val window: dynamic
fun getWindowSize(): Pair<Int, Int> {
return Pair(
window.innerWidth as Int,
window.innerHeight as Int
)
}
// 访问 document
external val document: dynamic
fun setTitle(title: String) {
document.title = title
}声明 JavaScript 库
kotlin
// 声明外部模块
@JsModule("lodash")
external object Lodash {
fun debounce(func: () -> Unit, wait: Int): () -> Unit
fun throttle(func: () -> Unit, wait: Int): () -> Unit
}
// 使用
fun setupSearch() {
val debouncedSearch = Lodash.debounce(::performSearch, 300)
document.getElementById("search").addEventListener("input", debouncedSearch)
}
fun performSearch() {
// 执行搜索
}TypeScript 定义映射
kotlin
// TypeScript:
// interface User {
// id: number;
// name: string;
// email?: string;
// }
// Kotlin 映射
external interface User {
var id: Int
var name: String
var email: String?
}
// 创建实例
fun createUser(): User = js("({id: 1, name: 'Alice'})")
// 或使用 json() 函数
fun createUser2(): User = json(
"id" to 1,
"name" to "Alice"
).unsafeCast<User>()DOM API 访问
标准代码块
kotlin
// jsMain
import org.w3c.dom.*
import kotlinx.browser.document
import kotlinx.browser.window
// 查询元素
fun getElementById(id: String): HTMLElement? {
return document.getElementById(id) as? HTMLElement
}
// 创建元素
fun createButton(text: String, onClick: () -> Unit): HTMLButtonElement {
return (document.createElement("button") as HTMLButtonElement).apply {
textContent = text
addEventListener("click", { onClick() })
}
}
// 添加元素
fun appendElement(parent: String, child: Element) {
document.getElementById(parent)?.appendChild(child)
}
// 示例:创建用户列表
fun renderUsers(users: List<User>) {
val container = document.getElementById("users") ?: return
container.innerHTML = ""
users.forEach { user ->
val div = document.createElement("div").apply {
className = "user-item"
innerHTML = """
<h3>${user.name}</h3>
<p>${user.email ?: ""}</p>
""".trimIndent()
}
container.appendChild(div)
}
}事件处理
kotlin
import org.w3c.dom.events.Event
import org.w3c.dom.events.MouseEvent
fun setupEventListeners() {
document.getElementById("submit-btn")?.addEventListener("click", { event ->
event.preventDefault()
submitForm()
})
document.getElementById("input")?.addEventListener("input", { event ->
val target = event.target as? HTMLInputElement
val value = target?.value
console.log("Input value: $value")
})
}Fetch API 与网络请求
标准代码块
kotlin
// jsMain
import kotlinx.browser.window
import kotlin.js.Promise
// 使用 dynamic 类型
suspend fun fetchData(url: String): String {
return window.fetch(url)
.then { response -> response.text() }
.await()
}
// 或声明 Fetch API
external fun fetch(url: String, init: dynamic = definedExternally): Promise<Response>
external interface Response {
fun text(): Promise<String>
fun json(): Promise<dynamic>
val ok: Boolean
val status: Int
}
// 使用示例
suspend fun getUser(id: String): User {
val response = fetch("/api/users/$id").await()
if (!response.ok) {
throw Exception("HTTP ${response.status}")
}
return response.json().await().unsafeCast<User>()
}
// POST 请求
suspend fun createUser(user: User): User {
val response = fetch("/api/users", js("""{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
}""")).await()
return response.json().await().unsafeCast<User>()
}JavaScript 调用 Kotlin
导出 Kotlin 函数
kotlin
// jsMain
@JsExport
fun greet(name: String): String {
return "Hello, $name!"
}
@JsExport
class Calculator {
fun add(a: Int, b: Int): Int = a + b
fun multiply(a: Int, b: Int): Int = a * b
}javascript
// JavaScript 使用
import { greet, Calculator } from './kotlin-app.mjs';
console.log(greet('Alice')); // "Hello, Alice!"
const calc = new Calculator();
console.log(calc.add(2, 3)); // 5自定义导出名称
kotlin
@JsExport
@JsName("formatCurrency")
fun format(amount: Double): String {
return "$$amount"
}javascript
import { formatCurrency } from './kotlin-app.mjs';
console.log(formatCurrency(99.99)); // "$99.99"NPM 依赖集成
Gradle 配置
kotlin
// build.gradle.kts
kotlin {
js(IR) {
browser {
commonWebpackConfig {
cssSupport {
enabled.set(true)
}
}
}
binaries.executable()
}
sourceSets {
val jsMain by getting {
dependencies {
// Kotlin/JS 标准库
implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.11.0")
// NPM 依赖
implementation(npm("axios", "1.6.2"))
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
}
}
}
}使用 NPM 包
kotlin
@file:JsModule("axios")
@file:JsNonModule
external object axios {
fun get(url: String): Promise<AxiosResponse>
fun post(url: String, data: dynamic): Promise<AxiosResponse>
}
external interface AxiosResponse {
val data: dynamic
val status: Int
}
// 使用
suspend fun fetchUsers(): List<User> {
val response = axios.get("/api/users").await()
return (response.data as Array<dynamic>)
.map { it.unsafeCast<User>() }
}Promise 与协程互操作
Promise 转协程
kotlin
import kotlin.js.Promise
// 扩展函数
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
then(
onFulfilled = { cont.resume(it) },
onRejected = { cont.resumeWithException(Exception(it.toString())) }
)
}
// 使用示例
suspend fun loadData() {
try {
val data = fetch("/api/data").await()
val json = data.json().await()
// 处理数据
} catch (e: Exception) {
console.error("Failed to load data", e)
}
}协程转 Promise
kotlin
fun loadDataAsync(): Promise<String> = GlobalScope.promise {
delay(1000)
fetchData()
}javascript
// JavaScript 调用
loadDataAsync()
.then(data => console.log(data))
.catch(error => console.error(error));Dynamic 类型使用
动态访问
kotlin
fun processData(data: dynamic) {
console.log(data.name) // 访问属性
console.log(data.items[0]) // 访问数组
data.method() // 调用方法
}
// 从 JSON 解析
fun parseUser(json: String): dynamic {
return JSON.parse(json)
}
val user = parseUser("""{"id":1,"name":"Alice"}""")
console.log(user.name) // "Alice"类型安全转换
kotlin
// 不安全转换
val user: User = dynamicData.unsafeCast<User>()
// 安全转换(带检查)
fun safeCast(data: dynamic): User? {
return try {
if (data.id != null && data.name != null) {
data.unsafeCast<User>()
} else {
null
}
} catch (e: Exception) {
null
}
}常见框架集成
React 集成
kotlin
@file:JsModule("react")
external interface ReactElement
external object React {
fun <P> createElement(
type: Any,
props: P,
vararg children: dynamic
): ReactElement
}
// 使用
fun MyComponent() = React.createElement(
"div",
json("className" to "container"),
"Hello, React!"
)Kotlin React DSL
kotlin
// 使用 kotlin-react
import react.*
import react.dom.*
val App = fc<Props> {
div {
className = "app"
h1 { +"Welcome to Kotlin/JS" }
button {
onClick = { console.log("Clicked!") }
+"Click Me"
}
}
}最佳实践
✅ 实践 1:声明外部接口而非使用 dynamic
kotlin
// ✅ 推荐
external interface ApiResponse {
val data: Array<User>
val status: String
}
// ❌ 不推荐
fun processResponse(response: dynamic) {
val data = response.data // 无类型检查
}✅ 实践 2:使用 @JsModule 声明第三方库
kotlin
// ✅ 类型安全
@JsModule("moment")
external object Moment {
fun (): MomentObj
}
external interface MomentObj {
fun format(pattern: String): String
}
// ❌ 动态调用
val moment: dynamic = js("require('moment')")✅ 实践 3:优先使用协程而非 Promise
kotlin
// ✅ 协程风格(清晰)
suspend fun loadUserData() {
val user = fetchUser().await()
val posts = fetchPosts(user.id).await()
render(user, posts)
}
// ❌ Promise 链式调用(复杂)
fun loadUserData(): Promise<Unit> {
return fetchUser()
.then { user -> fetchPosts(user.id) }
.then { posts -> /* ... */ }
}❌ 避免过度使用 js()
kotlin
// ❌ 不推荐
val result = js("someComplexJSCode()")
// ✅ 推荐:声明外部函数
external fun someComplexJSCode(): dynamic
val result = someComplexJSCode()构建配置
Webpack 配置
kotlin
// build.gradle.kts
kotlin {
js(IR) {
browser {
commonWebpackConfig {
devServer = devServer?.copy(
port = 3000,
open = true
)
cssSupport {
enabled.set(true)
}
}
webpackTask {
output.libraryTarget = "umd"
}
}
binaries.executable()
}
}模块系统选择
kotlin
kotlin {
js(IR) {
// UMD(Universal Module Definition)
browser {
webpackTask {
output.libraryTarget = "umd"
}
}
// CommonJS
nodejs {
/* ... */
}
}
}Kotlin/JS 提供了与 JavaScript 生态无缝集成的能力,允许开发者在保持类型安全的同时,充分利用 Web 平台的强大功能。