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.