Kotlin/JS:当Kotlin跑到浏览器里

说实话,我第一次听说Kotlin能编译成JavaScript时,第一反应是——这玩意儿靠谱吗?毕竟那时候前端已经是JavaScript/TypeScript的天下,突然冒出个JVM语言来抢饭碗,总觉得有点怪。但后来我在一个内部工具项目里试了一把,嗯,真香了。

Kotlin/JS说白了,就是把你的Kotlin代码编译成JavaScript,然后直接在浏览器里跑。你不用写一行JS,就能做前端开发。而且它和React的集成做得相当不错,还能直接调用npm上的海量包。今天我就带你捋一捋,这东西到底怎么用。

核心要点:Kotlin/JS ≠ 替代JavaScript,而是让你用Kotlin的语法和类型系统,去写前端逻辑。

Kotlin/JS 的两种编译模式

我刚开始接触时,最困惑的就是编译模式。Kotlin/JS有两种输出方式,你得先搞清楚它们。

模式 输出产物 适用场景
Legacy (旧版) 单个JS文件 + 依赖库 简单页面、小工具
IR (中间表示) 优化后的JS代码 大型项目、React集成

我个人建议,新项目直接用IR模式。为什么?因为IR模式生成的代码体积更小,而且对React的支持更原生。我在一个后台管理项目里试过Legacy模式,结果打包出来快2MB,换成IR后直接砍半。

小提示:在build.gradle.kts里设置 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/JS的React绑定库叫 kotlin-react,记得在依赖里加上。别问我怎么知道的——我曾经漏掉它,编译报错找了半小时。

npm 互操作:直接调用JS生态

Kotlin/JS最强大的地方,就是能直接调用npm上的包。你不用重新造轮子,现有的lodash、axios、moment等库都能用。

怎么用?三步走:

  1. build.gradle.kts 里声明npm依赖
  2. 写一个Kotlin外部声明(external declarations)
  3. 在代码里直接调用

举个例子,调用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签名

避坑指南:我曾经在调用一个npm包时,发现函数返回值类型对不上。后来才意识到,JS是动态类型,Kotlin是静态类型。你必须在external声明里精确指定类型,否则运行时就会崩。

前端开发实战:一个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知识结构。你看一眼,心里就有谱了。

Kotlin/JS 编译模式 Legacy → 单文件输出 IR → 优化代码、React友好 React集成 functionalComponent useState / useEffect kotlin-react 绑定库 npm互操作 @JsModule 注解 external 声明 npm() 依赖声明 用Kotlin写前端,享受类型安全 + JS生态 适合:内部工具、原型验证、全栈Kotlin项目

开发环境配置

最后,我分享一下我的开发环境配置。这东西看着琐碎,但配错了真的很浪费时间。

  • IDE:IntelliJ IDEA Ultimate(社区版也行,但少了JS调试支持)
  • 构建工具:Gradle + Kotlin DSL
  • 浏览器调试:Chrome DevTools + Kotlin/JS source maps
  • 热重载:webpack-dev-servervite(我更喜欢vite,启动快)
重要提醒:Kotlin/JS的source maps默认是开启的,但如果你用了混淆工具,记得关掉。我曾经在生产环境忘了关,结果用户能看到源码——那场面,尴尬得不行。

好了,Kotlin/JS的核心内容就这些。说白了,它就是让你用Kotlin的强类型和现代语法,去写前端代码。React集成让组件开发变得很自然,npm互操作又让你能直接用JS生态。如果你是个Kotlin后端开发者,想试试前端,这绝对是个不错的切入点。


公众号:蓝海资料掘金营,微信deep3321