When it comes to Kotlin, the first thing most people hear is that it’s a language for developing Android. It must be said that Google gave Kotlin more publicity than his founding company, Jetbrains.

Kotlin can write not only on Android, but also on the server side, so wherever you can write Java, you can replace Kotlin. Of course Kotlin is much more than that. Currently Kotlin can develop and use the following platforms. The JVM, android, js and native (beta). Kotlin now supports ios development as well, thanks to the ability to develop Native, although it doesn’t seem to work that well.

React with Kotlin today, this is just a try and use of KotlinJs.

The environment

  • Browser: Microsoft Edge 77.0.235.5
  • Kotlin: 1.3.12

Installation environment

React is a scaffolding for the React project, JetBrains/ create-React-Kotlin-app, and JetBrains/ Create-React-Kotlin-app. The second is JetBrains/ Kotlin-wrappers, which houses react ecosystem components such as Router, Redux, and so on.

Following the scaffolding documentation, one line of code generates our project.

npx create-react-kotlin-app my-app
Copy the code

After the project is generated, start the project and use NPM start or YARN start to start the project.

The project will automatically open the browser and see our project, indicating that our project has started.

We can take a look at the official wrappers library, which seems to have everything but a mature UI component library. Do you mean we have to write all the patterns ourselves?

Of course not.

Kotlinjs can be called to the NodeJS module, but it’s a bit annoying.

Install ant

Ant is a set of UI components developed by Ali front-end, including React version. The first step is to install the library.

Install the installation using YARN Add ANTd and wait until the installation is complete.

After the installation, we introduced the Ant demo into our project, opening index.css to introduce the project.

@import '~antd/dist/antd.css';
Copy the code

Let’s try to introduce a Button.

Then we see that on the page, there is the same button, which has nothing to do with ant’s style. Note that we are still not using Ant components in our project.

Binding UI controls

Create a new package UI/Ant to hold our classes bound to ant components.

How do we write our bound class? This was my first difficulty.

The first step is to go to our antd directory in node_modules and find the control directory we want to bind to.

We use @file:JsModule(“antd/lib/button”) to indicate the binding directory. Use @jsName (“default”) to bind. This control needs to be export default Button.

@file:JsModule("antd/lib/button")

package ui.ant

import react.RClass
import react.RProps

@JsName("default")
external val button: RClass<RProps>
Copy the code

Reintroduce the button we just wrote in our main.kt. Unlike react.dom.button, the declared button cannot set any properties, methods, etc. Of course we’ll set it up for him.

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(a) {
        div {
            ui.ant.button {
                +"Button"}}}}fun RBuilder.app(a) = child(App::class) {}
Copy the code

Wait for the automatic compilation and refresh to complete and view our page.

Here we have our buttons, and they look exactly as they appear in the official Ant documentation.

Our binding has now been successful.

Then do the same for the other controls.

We bind input to search.

This binding is a change from last time, and we can set some values for these two properties so that we can use them directly in the HTML DSL. Here we re-customize the properties we want to use by implementing the RProps, setting the Input to a placeholder, and SeacherRPorps can extend the properties directly from 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>
Copy the code

Bind the search control

@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>
Copy the code

Creating an independent Control

Let’s re-isolate a component called home.kt. Put our Button inside and use home.kt on app.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(a) {
        div{
           button{
               +"Button"}}}}fun RBuilder.home(a) = child(Home::class) {}
Copy the code

Wait for the page to refresh, still the same as before.

Add click events

Because we don’t declare click events in our buttons, we can’t call them directly, but KotlinJS can call them dynamically, using asDynamic we can call any HTML properties we want to use.

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

This completes the button click event, and when we click on the page, we can see the information printed on the browser console.

Bidirectional binding

So the data that we’re going to use is stored in RState, so let’s rewrite it to RState. State and props are two important properties in React. State is used to modify and update internal data, while props is used to pass in external data.

Customize HomeState to inherit RState to modify internal data.

interface HomeState : RState {
    var inputValue: Int
}
Copy the code

Change the RState type in our Home to HomeState

class Home : RComponent<RProps, HomeState>(a)Copy the code

This allows you to use the data in state below.

Write our data in a div.

div {
	+"${state.inputValue}"
}
Copy the code

Minor changes to button click events and changes to react data require calls to setState.

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

However, the page is refreshed with an error. TypeError: Cannot read property ‘toString’ of undefined Cannot read undefinded toString method.

It turns out that it is not enough to declare it in HomeState alone. You also need to assign the initial value in the overwrite method of homestate.init ().

Waiting for the page to refresh again, we saw what we expected. Every time you click the button, the numbers add up.

Cooperate with Axios

Axios is an HTTP request framework commonly used in the front end, and kotlin has given it a simple wrapper. We can use it right here.

First, again, install AxiOS

yarn add axios
Copy the code

After installation, create a new folder axios and then 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
}
Copy the code

Let’s make a simple application, enter the name, go to Github to find the relevant repository.

First we define our data class to hold the data we request.

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)
Copy the code

Then declare the classes to be used in HomeState.

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

Here is the code for the interface definition

div(classes = "search-input") {
    search {
        attrs {
            onChange = {
                val element = it.target as HTMLInputElement
                setState {
                    inputValue = element.value
                }
            }
            placeholder = "Please enter Github repository name"
        }
    }
}

button {
    +"Search"
    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}"
                    }
                }
            }
        }
    }
}
Copy the code

However, when we preview the interface at this point, we find that it is wrong again, indicating that Repos cannot iterate.

Why can’t we iterate over arrays? It turns out that repos was not initialized when the Home component was created. Here you need to understand the React lifecycle, and initialize the data before the component is created.

    override fun componentWillMount(a) {
        setState {
            repos = emptyArray()
        }
    }
Copy the code

The page now displays normally.

Improved button click events.

 button {
    +"Search"
    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)
            }
        }
    }
}
Copy the code

The project is now complete.

conclusion

First of all, the author is not a professional front end, so there are mistakes in the article also hope to correct; The more important purpose of this article is to illustrate kotlin’s use and experience in other areas.

When Kotlin writes React, the writing habit should be similar to that of TS, and it brings front-end writing experience to students at the back end. However, the ecology is incomplete. There is no official UI component library written by KT, and the compilation speed is slow, error reporting is not obvious sometimes, and there are few available data. To say nevertheless hard scalp development, individual feeling is completely ok, nevertheless estimate should oneself make many wheels.

Personal wechat official account