Kotlin/JS:当Kotlin跑到浏览器里
说实话,我第一次听说Kotlin能编译成JavaScript时,第一反应是——这玩意儿靠谱吗?毕竟那时候前端已经是JavaScript/TypeScript的天下,突然冒出个JVM语言来抢饭碗,总觉得有点怪。但后来我在一个内部工具项目里试了一把,嗯,真香了。
Kotlin/JS说白了,就是把你的Kotlin代码编译成JavaScript,然后直接在浏览器里跑。你不用写一行JS,就能做前端开发。而且它和React的集成做得相当不错,还能直接调用npm上的海量包。今天我就带你捋一捋,这东西到底怎么用。
Kotlin/JS 的两种编译模式
我刚开始接触时,最困惑的就是编译模式。Kotlin/JS有两种输出方式,你得先搞清楚它们。
| 模式 | 输出产物 | 适用场景 |
|---|---|---|
| Legacy (旧版) | 单个JS文件 + 依赖库 | 简单页面、小工具 |
| IR (中间表示) | 优化后的JS代码 | 大型项目、React集成 |
我个人建议,新项目直接用IR模式。为什么?因为IR模式生成的代码体积更小,而且对React的支持更原生。我在一个后台管理项目里试过Legacy模式,结果打包出来快2MB,换成IR后直接砍半。
js(IR) 即可切换到IR模式。
React 集成:用Kotlin写组件
React集成是Kotlin/JS最吸引我的地方。你想想看,能用Kotlin的data class、sealed class、协程来写React组件,那体验比写JSX舒服太多了。
先看一个最简单的组件:
// Counter.kt
import react.*
import react.dom.*
val Counter = functionalComponent<Props> {
val (count, setCount) = useState(0)
div {
h1 { +"点击次数: $count" }
button {
+"点我"
attrs.onClick = { setCount(count + 1) }
}
}
}
这段代码看着是不是很眼熟?其实就是React的函数组件,只不过用Kotlin语法写的。注意那个 + 号,它是Kotlin/JS里设置文本内容的语法糖,我第一次看到时也愣了一下。
我在项目中遇到过一个问题:Kotlin/JS的React组件和原生JS组件混用时,props的类型对不上。后来发现,只要用 @JsExport 注解标记组件,就能完美互调。
kotlin-react,记得在依赖里加上。别问我怎么知道的——我曾经漏掉它,编译报错找了半小时。
npm 互操作:直接调用JS生态
Kotlin/JS最强大的地方,就是能直接调用npm上的包。你不用重新造轮子,现有的lodash、axios、moment等库都能用。
怎么用?三步走:
- 在
build.gradle.kts里声明npm依赖 - 写一个Kotlin外部声明(external declarations)
- 在代码里直接调用
举个例子,调用lodash的 debounce 函数:
// build.gradle.kts
dependencies {
implementation(npm("lodash", "4.17.21"))
}
// Lodash.kt
@file:JsModule("lodash")
@file:JsNonModule
external fun debounce(
func: () -> Unit,
wait: Int
): () -> Unit
// 使用
val debouncedSave = debounce({
console.log("保存数据...")
}, 500)
这里的关键是 @JsModule 和 @JsNonModule 注解。前者告诉Kotlin这个函数来自哪个npm包,后者表示它不需要模块打包器也能用。嗯,这块有点绕,但记住一个原则:你写的external声明,就是JS函数的Kotlin签名。
前端开发实战:一个Todo List
光说不练假把式。我们来写一个完整的Todo List,把上面讲的知识点串起来。
先看项目结构:
src/
main/
kotlin/
App.kt // 主组件
TodoItem.kt // 单个待办项组件
TodoList.kt // 列表组件
styles.kt // 样式(用kotlin-css)
resources/
index.html // 入口HTML
核心代码:
// App.kt
import react.*
import react.dom.*
import kotlinx.css.*
val App = functionalComponent<Props> {
val (todos, setTodos) = useState(listOf(
"学习Kotlin/JS",
"写一个React组件",
"集成npm包"
))
val (input, setInput) = useState("")
div {
css {
padding = 20.px
fontFamily = "sans-serif"
}
h1 { +"我的待办清单" }
input {
attrs {
value = input
placeholder = "输入新任务"
onChange = { event ->
setInput(event.target.value)
}
}
}
button {
+"添加"
attrs.onClick = {
if (input.isNotBlank()) {
setTodos(todos + input)
setInput("")
}
}
}
ul {
todos.forEach { todo ->
li { +todo }
}
}
}
}
这段代码里,我用了 kotlinx.css 来写内联样式。说实话,用Kotlin写CSS的感觉很奇妙,类型安全,还能自动补全。不过要注意,kotlin-css 的API和原生CSS略有不同,比如 padding = 20.px 而不是 20px。
kotlin-css 太啰嗦,可以直接用 style 属性传字符串。但我不建议这么做——类型安全才是Kotlin的卖点。
知识体系总览
下面这张图,是我自己总结的Kotlin/JS知识结构。你看一眼,心里就有谱了。
开发环境配置
最后,我分享一下我的开发环境配置。这东西看着琐碎,但配错了真的很浪费时间。
- IDE:IntelliJ IDEA Ultimate(社区版也行,但少了JS调试支持)
- 构建工具:Gradle + Kotlin DSL
- 浏览器调试:Chrome DevTools + Kotlin/JS source maps
- 热重载:用
webpack-dev-server或vite(我更喜欢vite,启动快)
好了,Kotlin/JS的核心内容就这些。说白了,它就是让你用Kotlin的强类型和现代语法,去写前端代码。React集成让组件开发变得很自然,npm互操作又让你能直接用JS生态。如果你是个Kotlin后端开发者,想试试前端,这绝对是个不错的切入点。