當Kotlin愛上React,會發生什麼反應

NO IMAGE

說起 Kotlin,聽說過的大部人第一反應是一門開發 Android 的語言。不得不說 Google 對 Kotlin 的宣傳遠遠的大於了 Kotlin 的創始公司 Jetbrains 。

Kotlin 不僅僅是能寫Android,而且可以寫服務端,可以說只要可以寫Java的地方,就可以用Kotlin來進行替換。當然Kotlin遠不止這些。目前Kotlin可以做到以下平臺的開發和使用。jvm,android,js和native(beta)。正因為可以開發 Native ,所以Kotlin現在對 ios 開發也是支持的,雖然看起來效果不是那麼的好。

當Kotlin愛上React,會發生什麼反應

今天用 Kotlin 進行 react 開發,這只是對 KotlinJs 的一次嘗試和使用。

環境

  • 瀏覽器:Microsoft Edge 77.0.235.5 (官方內部版本) dev(64 位)
  • Kotlin: 1.3.12

安裝環境

很早以前,kotlin 官網就出現了兩個庫,這兩個庫基本就是來完善 react 在 kotlin 中的生態,第一個是 JetBrains/create-react-kotlin-app ,一個生成 React 工程的腳手架;第二個是JetBrains/kotlin-wrappers ,該庫裡存放了 react 周邊生態組件,比如說 router,redux 等等。

按照腳手架文檔,一行代碼便能生成我們的項目。

npx create-react-kotlin-app my-app

在項目生成後,打開項目,使用 npm start 或者 yarn start 啟動項目。

當Kotlin愛上React,會發生什麼反應

項目會自動打開瀏覽器,然後看到我們的項目,說明我們的項目便啟動了。

我們可以看看官方提供的 wrappers 庫裡有什麼,好像什麼都有就是缺少一個成熟的 UI 組件庫。難道說所有的樣式都要我們自己寫?

當然不是。

kotlinjs 是可以調用到 nodejs 模塊的,只不過啊,有點煩。

安裝 ant

ant 是 阿里前端 開發的一套 UI 組件,其中有著 React 版本。首先要安裝該庫。

通過 yarn add antd 進行安裝,等待安裝完成。

安裝結束後,我們將 ant的演示引入到我們的項目中,打開 index.css 引入項目。

@import '~antd/dist/antd.css';

我們嘗試的引入一個 Button 看看。

然後我們看到頁面上,還是原來的button ,和 ant 的風格沒有半毛錢關係。說明我們還是沒有講ant的組件使用到我們的項目中去。

綁定 UI 控件

新建立一個包 ui/ant 來存放我們與 ant 組件的綁定類。

如何編寫我們的綁定類?這是我遇到的第一個難題。

首先要找到我們的 node_modules中的antd目錄,找到我們要綁定的控件目錄。

當Kotlin愛上React,會發生什麼反應

我們用 @file:JsModule("antd/lib/button") 來表明綁定目錄。通過 @JsName("default") 來進行綁定,此時需要該控件是以 export default Button;

@file:JsModule("antd/lib/button")
package ui.ant
import react.RClass
import react.RProps
@JsName("default")
external val button: RClass<RProps>

在我們的 Main.kt 中重新引入我們剛剛寫的 button。此時的 button 不同於 react.dom.button ,我們所聲明的 button 無法直接設置任何屬性,方法等。當然我們會給他設置的。

package app
import react.RBuilder
import react.RComponent
import react.RProps
import react.RState
import react.dom.div
class App : RComponent<RProps, RState>() {
override fun RBuilder.render() {
div {
ui.ant.button {
+"Button"
}
}
}
}
fun RBuilder.app() = child(App::class) {}

等待自動編譯和刷新完成,查看我們的頁面。

當Kotlin愛上React,會發生什麼反應

終於見到了我們的按鈕了,而且樣式和 Ant 官方文檔上所示一致。

說明我們的綁定現在已經成功。

然後如法炮製,把其他的要使用控件進行同樣的綁定。

我們 input 和 search 進行綁定。

此次綁定與上次有所改變,我們對這兩個屬性可以設置一些值,以便我們在 HTML DSL 中直接使用。這裡我們通過實現 RProps 來重新定製了我們要使用的屬性,對 Input 設置了一個 placeholder,而 SeacherRPorps 可以直接繼承 InputRProps 來進行屬性上的擴展。

@file:JsModule("antd/lib/input/Input")
package ui.antd
import react.*
external interface InputRProps:RProps {
var placeholder:String
}
@JsName("default")
external val input: RClass<InputProps>

對 search 控件進行綁定

@file:JsModule("antd/lib/input/Search")
package ui.antd
import org.w3c.dom.events.Event
import react.*
external interface SearchProps : InputRProps {
var onChange: (Event) -> Unit
var value: String
}
@JsName("default")
external val search: RClass<SearchProps>

新建獨立控件

我們重新獨立出來一個組件,叫做 Home.kt 。將我們的 Button 存放進去,然後在 App.kt 上 使用 Home.kt。

package home
import react.RBuilder
import react.RComponent
import react.RProps
import react.RState
import react.dom.div
import ui.ant.button
class Home : RComponent<RProps, RState>() {
override fun RBuilder.render() {
div{
button{
+"Button"
}
}
}
}
fun RBuilder.home() = child(Home::class) {}

等待頁面刷新完畢,仍舊是原來的樣子。

添加點擊事件

因為我們的按鈕中沒有聲明點擊事件,所以無法直接調用,但是 kotlinjs 可以動態屬性調用,通過 asDynamic 便可以調用我們想要使用的任何 html 中存在的屬性。

 button {
+"Button"
attrs {
asDynamic().onClick = {
console.log("on click")
}
}
}

此時就完成了按鈕的點擊事件,當我們在頁面中點擊時,可以看到瀏覽器控制檯上的信息進行打印。

進行雙向綁定

對我們要使用的數據存放在 RState 中,我們重寫來寫 RState。 state 和 props 是 react 中兩個重要的屬性,state 是用來進行內部數據的修改更新,而 props 是可以將外部數據進行傳入。

自定義 HomeState 來繼承 RState,來進行對內部數據的修改。

interface HomeState : RState {
var inputValue: Int
}

將我們 Home 中的 RState 類型改為 HomeState

class Home : RComponent<RProps, HomeState>()

這樣便可以在下方使用 state 中的數據。

用一個 div 來寫我們的數據。

div {
+"${state.inputValue}"
}

對 button 中的點擊事件進行微小的修改,在 react 中對數據進行修改都需要調用 setState 。

button {
+"Button"
attrs.asDynamic().onClick ={
setState {
inputValue += 1
}
}
}

然而,頁面刷新後卻是一個報錯。TypeError: Cannot read property ‘toString’ of undefined 無法讀取 undefinded 的 toString 方法。

原來,單在 HomeState 中聲明是不夠的,還需要在 HomeState.init() 的重寫方法中進行初始值的賦值。

重新等待頁面刷新,出現了我們預想的結果。每次點擊按鈕就會數字就會進行累加。

配合 Axios

axios 是前端中常用的一個 http 請求框架,kotlin 官方已經對它進行了一次簡單的封裝。我們這裡直接就可以使用。

首先仍舊是先安裝 axios

yarn add axios

安裝完成後新建一個文件夾axios,再新建Axios.kt。

package axios
import kotlin.js.Promise
@JsModule("axios")
external fun <T> axios(config: AxiosConfigSettings): Promise<AxiosResponse<T>>
// Type definition
external interface AxiosConfigSettings {
var url: String
var method: String
var baseUrl: String
var timeout: Number
var data: dynamic
var transferRequest: dynamic
var transferResponse: dynamic
var headers: dynamic
var params: dynamic
var withCredentials: Boolean
var adapter: dynamic
var auth: dynamic
var responseType: String
var xsrfCookieName: String
var xsrfHeaderName: String
var onUploadProgress: dynamic
var onDownloadProgress: dynamic
var maxContentLength: Number
var validateStatus: (Number) -> Boolean
var maxRedirects: Number
var httpAgent: dynamic
var httpsAgent: dynamic
var proxy: dynamic
var cancelToken: dynamic
}
external interface AxiosResponse<T> {
val data: T
val status: Number
val statusText: String
val headers: dynamic
val config: AxiosConfigSettings
}

我們做一個簡單的應用,通過輸入名字,到 Github 上尋找相關的倉庫。

首先定義我們的數據類,來存放我們請求獲取到的數據。

data class Result(
val total_count: Int,
val items: Array<Item>
)
data class Item(val id: Long,
val node_id: String,
val name: String,
val full_name: String,
val html_url: String)

然後在 HomeState 中聲明所使用的類。

interface HomeState : RState {
var inputValue: String
var repos: Array<Item>
}

下面是界面定義的代碼

div(classes = "search-input") {
search {
attrs {
onChange = {
val element = it.target as HTMLInputElement
setState {
inputValue = element.value
}
}
placeholder = "請輸入 Github 倉庫名稱"
}
}
}
button {
+"搜索"
attrs {
asDynamic().onClick = {
}
}
}
div(classes = "list") {
if (state.repos.isNotEmpty()) {
ul {
for ((index, item) in state.repos.withIndex()) {
li {
a(href = item.html_url) {
+"${index + 1} / ${item.full_name}"
}
}
}
}
}
}

不過當我們此時預覽界面的時候,發現又又又錯了,提示 repos 無法進行迭代。

明明是數組為什麼無法迭代呢?原來是在 Home 組件創建的時候,還沒有對 repos 進行初始化。這裡需要了解 react 的生命週期,在組件建立前,對數據進行初始化。

    override fun componentWillMount() {
setState {
repos = emptyArray()
}
}

此時頁面可以正常展示了。

完善 button 的點擊事件。

 button {
+"搜索"
attrs {
asDynamic().onClick = {
//https://api.github.com/search/repositories?q=
val config: AxiosConfigSettings = jsObject {
url = "https://api.github.com/search/repositories?q=${state.inputValue}"
}
axios.axios<Result>(config).then { response ->
setState {
repos = response.data.items
}
}.catch { error ->
console.log("error", error)
}
}
}
}

此時便完成了項目。

當Kotlin愛上React,會發生什麼反應

總結

首先聲明 作者並非專業前端,所以文章中有錯誤還望指正;本文更重要的目的是為了表明 kotlin 在其他領域的使用和使用體驗。

kotlin 寫 react,在書寫習慣上應該和 ts 寫是差不多的,而且給後端同學帶來了寫前端的體驗。但是生態不完整,就一個 UI 組件庫沒有一個官方用 kt 寫的,而且編譯速度慢,報錯有時不明顯,可查閱資料少。不過要說硬著頭皮開發,個人感覺是完全可以的,不過估計要自己造不少輪子。

個人微信公眾號

當Kotlin愛上React,會發生什麼反應

相關文章

Node.js使用JWT對接SSO

詳談webpack4

RocketMQ源碼分析之路由中心(NameServer)

一文理解Netty模型架構