[Ktjs x React] 前置知识,ktjs 与 js

框架

浏览数:91

2019-8-21

AD:资源代下载服务

终于还是决定开这个坑,Kotlin 和 React 都是我非常喜欢的技术,之前也看到了 Kotlin 官方提供了针对 React 的工程向导,体验过一把后直接入坑。

在入坑之前,还是决定先写这么一篇,把 ktjs 讲一番,以便后续可以更好的理解 ktjs + react,同时也是由于大家对 ktjs 都不太了解,也正好是时候扫扫盲,毕竟这么强大的东西只拿来写 Android 实在是太可惜了。

一、建立项目

在 IntelliJ IDEA 的 新建向导内,可以选择 Kotlin/JS 来建立一个基于 Kotlin 的 Js 项目:

新建项目

填写完基本的要素后,项目就建好了。

接下来要修改一下 build.gradle ,不得不再说一句,JetBrains 做插件真的每次都不做完善,总得改一大堆东西,每次我都忍不住重写各种生成工具,以下是修改后的 build.gradle

plugins{
    id 'kotlin2js' version '1.3.20'
}

group 'com.rarnu'    // 此处改成你自己的 group
version '1.0'        // 此处改你自己的版本号

repositories {
    mavenCentral()
    jcenter()
    google()
}

sourceSets.main {
    kotlin.srcDirs += 'src/main/kotlin'
    resources.srcDirs += 'src/main/resources'
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js"
    compile "org.jetbrains.kotlinx:kotlinx-html-js:0.6.9"
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.2.0"
    compile "org.jetbrains.kotlinx:kotlinx-io-js:0.1.8"
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-io-js:0.1.8"
    testImplementation "org.jetbrains.kotlin:kotlin-test-js"
}

compileKotlin2Js {
    kotlinOptions.outputFile = "${projectDir}/web/scripts/main.js"
    kotlinOptions.moduleKind = "umd"
    kotlinOptions.sourceMap = true
}

build.doLast() {
    configurations.compile.each { File file ->
        copy {
            includeEmptyDirs = false
            from zipTree(file.absolutePath)
            into "${projectDir}/web/lib"
            include { fileTreeElement ->
                def path = fileTreeElement.path
                path.endsWith(".js") && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
            }
        }
    }
    copy {
        includeEmptyDirs = false
        from new File("src/main/resources")
        into "web"
    }
}

需要注意的是,这里我加了一个 build.doLast(),它指出了在编译完成后,需要拷贝文件到指定目录,因为我们最终的目的还是让项目跑起来,它需要一个完整的 web 项目的结构。

二、使用 ktjs 来生成页面

下面来新建一个页面,命名为 Main.kt,内容如下:

import kotlinx.html.button
import kotlinx.html.dom.create
import kotlinx.html.h1
import kotlinx.html.js.div
import kotlinx.html.js.onClickFunction
import kotlin.browser.document

@JsName("main")
fun main(args: Array<String>) {
    val root = document.getElementById("root")
    val div = document.create.div {
        h1 { text("Hello Ktjs") }
        button {
            text("Click Me!")
            onClickFunction = { println("Clicked!!!") }
        }
    }
    root?.append(div)
}

从字面意思就很容易理解这段代码做了什么,首先找到页面内的 root 元素,然后向该元素内添加一个 div,该 div 内包含一个 h1 和一个 button。需要注意的是,Ktjs 和通常的 Kotlin 程序一样,以 main 函数作为程序的开始,当加载到这个 js 的时候,即会主动执行 main 函数。如果不需要执行 main 函数,可以将该函数置空。

在这里也能够清晰的看到,在生成元素时,可以直接绑定它的 onClick 事件。

或许你会不理解,在等号后面的代码段是怎么来的,为什么可以这么写,其实 onClickFunction 的声明是这样的:

var CommonAttributeGroupFacade.onClickFunction : (Event) -> Unit

所以你可以直接在这里接一个符合声明的函数,这里省略了 Event 参数,按正规的 js 写法应当是:

onClickFunction = { event ->println("Clicked!!!") }

在不声明 event 的情况下,这个参数会被 Kotlin 默认为 it,所以你也可以把代码改成这样:

onClickFunction = { println("Clicked, $it") }

好了,现在写一个简单的页面,命名为 index.html 并放到 resources 目录内:

<html>
<body>
<div id="root"></div>
<script type="text/javascript" src="./lib/kotlin.js"></script>
<script type="text/javascript" src="./lib/kotlinx-html-js.js"></script>
<script type="text/javascript" src="./scripts/main.js"></script>
</body>
</html>

编译运行吧,可以看到页面已经成功生成了:

运行项目

三、操作页面元素

上面的例子允许你在 Kotlin 内直接编写页面,然而对于复杂的页面来说,这么做无疑是浪费时间,我们有更简单的方法可以写出 html,更多的场景是用 js 直接操作页面元素,比如说为按钮绑定事件。

下面来看一个例子,首先还是 html 页面,为了方便起见,只写一个按钮:

<button id='btn'>Sample</button>

随后为它绑定一个 onClick 事件:

@JsName("main")
fun main(args: Array<String>) {
    val btn = document.getElementById("btn").asDynamic()
    btn.onclick = { println("button clicked!") }
}

此时在页面中的按钮就拥有了点击事件。同样的,我们也可以为页面中的元素设置各种属性,动态的修改页面的样式等。

四、Ktjs 导出

导出是为了让普通的 js 可以使用 Ktjs 写出来的函数,为了能正常的加载 Ktjs 的模块,我们需要引入 require.js:

<script type="text/javascript" src="./lib/require.js"></script>

要在编译时自动带上 require.js,可以将该 js 放到项目的 resources/lib 下,编译后该 js 即可以自动被拷贝到 web/lib 内。

Ktjs 可以导出类以及函数,例如以下类定义和函数定义:

class SampleClass(val name: String){
    fun use() { println(name) }
}
@JsName("sayHello")
fun sayHello(name: String) = "Hello, $name"

需要注意的是,Ktjs 可以直接导出类,只要声明就可以,而导出函数时,必须加上 @JsName 注解以指出导出名称,否则编译器会在函数名后加上一个数字后缀,对调用函数造成影响。当然了,系统的做法是为了防止函数名称重复,所以我们也需要在写代码时注意命名的问题。

导出后可以在 js 代码内使用导出的类或函数:

<script type="text/javascript" src="./lib/require.js"></script>
<script type="text/javascript">
    requirejs.config({
        paths: {
            'kotlin': 'lib/kotlin',
            'kotlinx-html-js': 'lib/kotlinx-html-js',
            'main': 'scripts/main'
        }
    });
    requirejs(["main"], function (module) {
        var sample = new module.SampleClass("rarnu");
        sample.use();
        module. sayHello("ktjs");
    });
</script>

此处使用 require.js 来加载模块,就不再需要直接引入 kotlin.jskotlin-html-js.js 了,全部让 require.js 来管理。加载完毕后的回调函数内,module 对象就是被加载的模块,可以直接从中获取对象。

五、Ktjs 调用 js

相比 js 调用 ktjs,反过来的操作太简单了,ktjs 内提供了一个 js() 函数,可以直接执行 js 代码。假设在 html 内有如下的 js 方法定义:

<script type="text/javascript">
    function jsFunc() {
        console.log("js func");
        return 0;
    }
</script>

则在 ktjs 只需以下代码就可以调用了:

val ret = js("jsFunc();")
println(ret)                  // 此处得到 0

前置的知识到此已经全部讲完了,我上传了一个 Demo 程序(点此进入)供各位参考,后面再写就是真正的 Ktjs x React 了。

作者:何晓杰Dev