# 教程 - 使用 React 和 Kotlin/JS 创建 Web 应用程序
This tutorial will teach you how to build a browser application with Kotlin/JS and the [React](https://reactjs.org/) framework. You will:
* Complete common tasks associated with building a typical React application.
* Explore how [Kotlin's DSLs](type-safe-builders.html) can be used to help express concepts concisely and uniformly without sacrificing readability, allowing you to write a full-fledged application completely in Kotlin.
* Learn how to use ready-made npm components, use external libraries, and publish the final application.
The output will be a KotlinConf Explorer web app dedicated to the [KotlinConf](https://kotlinconf.com/) event, with links to conference talks. Users will be able to watch all the talks on one page and mark them as seen or unseen.
The tutorial assumes you have prior knowledge of Kotlin and basic knowledge of HTML and CSS. Understanding the basic concepts behind React may help you understand some sample code, but it is not strictly required.
> **Note:**
> You can get the final application [here](https://github.com/kotlin-hands-on/web-app-react-kotlin-js-gradle/tree/finished).
## Before you start
1. Download and install the latest version of [IntelliJ IDEA](https://www.jetbrains.com/idea/download/).
2. Clone the [project template](https://github.com/kotlin-hands-on/web-app-react-kotlin-js-gradle) and open it in IntelliJ IDEA. The template includes a basic Kotlin Multiplatform Gradle project with all required configurations and dependencies
* Dependencies and tasks in the `build.gradle.kts` file:
```KOTLIN
dependencies {
// React, React DOM + Wrappers
implementation(enforcedPlatform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:1.0.0-pre.430"))
implementation("org.jetbrains.kotlin-wrappers:kotlin-react")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom")
// Kotlin React Emotion (CSS)
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion")
// Video Player
implementation(npm("react-player", "2.12.0"))
// Share Buttons
implementation(npm("react-share", "4.4.1"))
// Coroutines & serialization
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
```
* An HTML template page in `src/jsMain/resources/index.html` for inserting JavaScript code that you'll be using in this tutorial:
```HTML
Hello, Kotlin/JS!
```
Kotlin/JS projects are automatically bundled with all of your code and its dependencies into a single JavaScript file with the same name as the project, `confexplorer.js`, when you build them. As a typical [JavaScript convention](https://faqs.skillcrush.com/article/176-where-should-js-script-tags-be-linked-in-html-documents), the content of the body (including the `root` div) is loaded first to ensure that the browser loads all page elements before the scripts.
* A code snippet in `src/jsMain/kotlin/Main.kt`: ```KOTLIN import kotlinx.browser.document fun main() { document.bgColor = "red" } ```
### Run the development server
By default, the Kotlin Multiplatform Gradle plugin comes with support for an embedded `webpack-dev-server`, allowing you to run the application from the IDE without manually setting up any servers.
To test that the program successfully runs in the browser, start the development server by invoking the `run` or `browserDevelopmentRun` task (available in the `other` or `kotlin browser` directory) from the Gradle tool window inside IntelliJ IDEA:

To run the program from the Terminal, use `./gradlew run` instead.
When the project is compiled and bundled, a blank red page will appear in a browser window:

### Enable hot reload / continuous mode
Configure [continuous compilation](dev-server-continuous-compilation.html) mode so you don't have to manually compile and execute your project every time you make changes. Make sure to stop all running development server instances before proceeding.
1. Edit the run configuration that IntelliJ IDEA automatically generates after running the Gradle `run` task for the first time:

2. In the Run/Debug Configurations dialog, add the `--continuous` option to the arguments for the run configuration:

After applying the changes, you can use the Run button inside IntelliJ IDEA to start the development server back up. To run the continuous Gradle builds from the Terminal, use `./gradlew run --continuous` instead.
3. To test this feature, change the color of the page to blue in the `Main.kt` file while the Gradle task is running:
```KOTLIN
document.bgColor = "blue"
```
The project then recompiles, and after a reload the browser page will be the new color.
You can keep the development server running in continuous mode during the development process. It will automatically rebuild and reload the page when you make changes.
> **Note:**
> You can find this state of the project on the `master` branch [here](https://github.com/kotlin-hands-on/web-app-react-kotlin-js-gradle/tree/master).
## Create a web app draft
### Add the first static page with React
To make your app display a simple message, replace the code in the `Main.kt` file with the following:
```KOTLIN
import kotlinx.browser.document
import react.*
import emotion.react.css
import csstype.Position
import csstype.px
import react.dom.html.ReactHTML.h1
import react.dom.html.ReactHTML.h3
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.p
import react.dom.html.ReactHTML.img
import react.dom.client.createRoot
import kotlinx.serialization.Serializable
fun main() {
val container = document.getElementById("root") ?: error("Couldn't find root container!")
createRoot(container).render(Fragment.create {
h1 {
+"Hello, React+Kotlin/JS!"
}
})
}
```
* The `render()` function instructs [kotlin-react-dom](https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-react-dom) to render the first HTML element inside a [fragment](https://reactjs.org/docs/fragments.html) to the `root` element. This element is a container defined in `src/jsMain/resources/index.html`, which was included in the template.
* The content is an `` header and uses a typesafe DSL to render HTML.
* `h1` is a function that takes a lambda parameter. When you add the `+` sign in front of a string literal, the `unaryPlus()` function is actually invoked using [operator overloading](operator-overloading.html). It appends the string to the enclosed HTML element.
When the project recompiles, the browser displays this HTML page:

### Convert HTML to Kotlin's typesafe HTML DSL
The Kotlin [wrappers](https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-react/README.md) for React come with a [domain-specific language (DSL)](type-safe-builders.html) that makes it possible to write HTML in pure Kotlin code. In this way, it's similar to [JSX](https://reactjs.org/docs/introducing-jsx.html) from JavaScript. However, with this markup being Kotlin, you get all the benefits of a statically typed language, such as autocomplete or type checking.
Compare the classic HTML code for your future web app and its typesafe variant in Kotlin:
HTML:
```HTML
KotlinConf Explorer
Videos to watch
John Doe: Building and breaking things
Jane Smith: The development process
Matt Miller: The Web 7.0
Videos watched
Tom Jerry: Mouseless development
John Doe: Building and breaking things
```
Kotlin:
```KOTLIN
h1 {
+"KotlinConf Explorer"
}
div {
h3 {
+"Videos to watch"
}
p {
+ "John Doe: Building and breaking things"
}
p {
+"Jane Smith: The development process"
}
p {
+"Matt Miller: The Web 7.0"
}
h3 {
+"Videos watched"
}
p {
+"Tom Jerry: Mouseless development"
}
}
div {
h3 {
+"John Doe: Building and breaking things"
}
img {
src = "https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder"
}
}
```
Copy the Kotlin code and update the `Fragment.create()` function call inside the `main()` function, replacing the previous `h1` tag.
Wait for the browser to reload. The page should now look like this:

### Add videos using Kotlin constructs in markup
There are some advantages to writing HTML in Kotlin using this DSL. You can manipulate your app using regular Kotlin constructs, like loops, conditions, collections, and string interpolation.
You can now replace the hardcoded list of videos with a list of Kotlin objects:
1. In `Main.kt`, create a `Video` [data class](data-classes.html) to keep all video attributes in one place:
```KOTLIN
data class Video(
val id: Int,
val title: String,
val speaker: String,
val videoUrl: String
)
```
2. Fill up the two lists, for unwatched videos and watched videos, respectively. Add these declarations at file-level in `Main.kt`:
```KOTLIN
val unwatchedVideos = listOf(
Video(1, "Opening Keynote", "Andrey Breslav", "https://youtu.be/PsaFVLr8t4E"),
Video(2, "Dissecting the stdlib", "Huyen Tue Dao", "https://youtu.be/Fzt_9I733Yg"),
Video(3, "Kotlin and Spring Boot", "Nicolas Frankel", "https://youtu.be/pSiZVAeReeg")
)
val watchedVideos = listOf(
Video(4, "Creating Internal DSLs in Kotlin", "Venkat Subramaniam", "https://youtu.be/JzTeAM8N1-o")
)
```
3. To use these videos on the page, write a Kotlin `for` loop to iterate over the collection of unwatched `Video` objects. Replace the three `p` tags under "Videos to watch" with the following snippet:
```KOTLIN
for (video in unwatchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
}
```
4. Apply the same process to modify the code for the single tag following "Videos watched" as well:
```KOTLIN
for (video in watchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
}
```
Wait for the browser to reload. The layout should stay the same as before. You can add some more videos to the list to make sure that the loop is working.
### Add styles with typesafe CSS
The [kotlin-emotion](https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-emotion/) wrapper for the [Emotion](https://emotion.sh/docs/introduction) library makes it possible to specify CSS attributes – even dynamic ones – right alongside HTML with JavaScript. Conceptually, that makes it similar to [CSS-in-JS](https://reactjs.org/docs/faq-styling.html#what-is-css-in-js) – but for Kotlin. The benefit of using a DSL is that you can use Kotlin code constructs to express formatting rules.
The template project for this tutorial already includes the dependency needed to use `kotlin-emotion`:
```KOTLIN
dependencies {
// ...
// Kotlin React Emotion (CSS) (chapter 3)
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion")
// ...
}
```
With `kotlin-emotion`, you can specify a `css` block inside HTML elements `div` and `h3`, where you can define the styles.
To move the video player to the top right-hand corner of the page, use CSS and adjust the code for the video player (the last `div` in the snippet):
```KOTLIN
div {
css {
position = Position.absolute
top = 10.px
right = 10.px
}
h3 {
+"John Doe: Building and breaking things"
}
img {
src = "https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder"
}
}
```
Feel free to experiment with some other styles. For example, you could change the `fontFamily` or add some `color` to your UI.
## Design app components
The basic building blocks in React are called [components](https://reactjs.org/docs/components-and-props.html). Components themselves can also be composed of other, smaller components. By combining components, you build your application. If you structure components to be generic and reusable, you'll be able to use them in multiple parts of the app without duplicating code or logic.
The content of the `render()` function generally describes a basic component. The current layout of your application looks like this:

If you decompose your application into individual components, you'll end up with a more structured layout in which each component handles its responsibilities:

Components encapsulate a particular functionality. Using components shortens source code and makes it easier to read and understand.
### Add the main component
To start creating the application's structure, first explicitly specify `App`, the main component for rendering to the `root`element:
1. Create a new `App.kt` file in the `src/jsMain/kotlin` folder.
2. Inside this file, add the following snippet and move the typesafe HTML from `Main.kt` into it:
```KOTLIN
import kotlinx.coroutines.async
import react.*
import react.dom.*
import kotlinx.browser.window
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import emotion.react.css
import csstype.Position
import csstype.px
import react.dom.html.ReactHTML.h1
import react.dom.html.ReactHTML.h3
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.p
import react.dom.html.ReactHTML.img
val App = FC {
// typesafe HTML goes here, starting with the first h1 tag!
}
```
The `FC` function creates a [function component](https://reactjs.org/docs/components-and-props.html#function-and-class-components).
3. In the `Main.kt` file, update the `main()` function as follows:
```KOTLIN
fun main() {
val container = document.getElementById("root") ?: error("Couldn't find root container!")
createRoot(container).render(App.create())
}
```
Now the program creates an instance of the `App` component and renders it to the specified container.
For more information about React concepts, see the [documentation and guides](https://reactjs.org/docs/hello-world.html#how-to-read-this-guide).
### Extract a list component
Since the `watchedVideos` and `unwatchedVideos` lists each contain a list of videos, it makes sense to create a single reusable component, and only adjust the content displayed in the lists.
The `VideoList` component follows the same pattern as the `App` component. It uses the `FC` builder function, and contains the code from the `unwatchedVideos` list.
1. Create a new `VideoList.kt` file in the `src/jsMain/kotlin` folder and add the following code:
```KOTLIN
import kotlinx.browser.window
import react.*
import react.dom.*
import react.dom.html.ReactHTML.p
val VideoList = FC {
for (video in unwatchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
}
}
```
2. In `App.kt`, use the `VideoList` component by invoking it without parameters:
```KOTLIN
// . . .
div {
h3 {
+"Videos to watch"
}
VideoList()
h3 {
+"Videos watched"
}
VideoList()
}
// . . .
```
For now, the `App` component has no control over the content that is shown by the `VideoList` component. It's hard-coded, so you see the same list twice.
### Add props to pass data between components
Since you're going to reuse the `VideoList` component, you'll need to be able to fill it with different content. You can add the ability to pass the list of items as an attribute to the component. In React, these attributes are called props. When the props of a component are changed in React, the framework automatically re-renders the component.
For `VideoList`, you'll need a prop containing the list of videos to be shown. Define an interface that holds all the props which can be passed to a `VideoList` component:
1. Add the following definition to the `VideoList.kt` file:
```KOTLIN
external interface VideoListProps : Props {
var videos: List