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

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

Kotlin 不只僅是能寫Android,並且能夠寫服務端,能夠說只要能夠寫Java的地方,就能夠用Kotlin來進行替換。固然Kotlin遠不止這些。目前Kotlin能夠作到如下平臺的開發和使用。jvm,android,js和native(beta)。正由於能夠開發 Native ,因此Kotlin如今對 ios 開發也是支持的,雖然看起來效果不是那麼的好。html

今天用 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 等等。node

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

npx create-react-kotlin-app my-app
複製代碼

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

項目會自動打開瀏覽器,而後看到咱們的項目,說明咱們的項目便啓動了。ios

咱們能夠看看官方提供的 wrappers 庫裏有什麼,好像什麼都有就是缺乏一個成熟的 UI 組件庫。難道說全部的樣式都要咱們本身寫?git

固然不是。github

kotlinjs 是能夠調用到 nodejs 模塊的,只不過啊,有點煩。shell

安裝 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目錄,找到咱們要綁定的控件目錄。

咱們用 @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) {}
複製代碼

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

終於見到了咱們的按鈕了,並且樣式和 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 在其餘領域的使用和使用體驗。

kotlin 寫 react,在書寫習慣上應該和 ts 寫是差很少的,並且給後端同窗帶來了寫前端的體驗。可是生態不完整,就一個 UI 組件庫沒有一個官方用 kt 寫的,並且編譯速度慢,報錯有時不明顯,可查閱資料少。不過要說硬着頭皮開發,我的感受是徹底能夠的,不過估計要本身造很多輪子。

我的微信公衆號

相關文章
相關標籤/搜索