提及 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 的一次嘗試和使用。前端
很早之前,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 是 阿里前端 開發的一套 UI 組件,其中有着 React 版本。首先要安裝該庫。
經過 yarn add antd
進行安裝,等待安裝完成。
安裝結束後,咱們將 ant的演示引入到咱們的項目中,打開 index.css 引入項目。
@import '~antd/dist/antd.css';
複製代碼
咱們嘗試的引入一個 Button 看看。
而後咱們看到頁面上,仍是原來的button ,和 ant 的風格沒有半毛錢關係。說明咱們仍是沒有講ant的組件使用到咱們的項目中去。
新創建一個包 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 是前端中經常使用的一個 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 寫的,並且編譯速度慢,報錯有時不明顯,可查閱資料少。不過要說硬着頭皮開發,我的感受是徹底能夠的,不過估計要本身造很多輪子。