ReactとKotlin/JSでWebアプリケーションを構築する — チュートリアル
このチュートリアルでは、Kotlin/JS と React フレームワークを使ってブラウザアプリケーションを構築する方法を学びます。以下を行います。
- 一般的な React アプリケーションの構築に関連する一般的なタスクを完了します。
- Kotlin の DSL を使用して、読みやすさを犠牲にすることなく、概念を簡潔かつ均一に表現する方法を探求し、Kotlin で本格的なアプリケーションを記述できるようにします。
- 既製の npm コンポーネントの使用方法、外部ライブラリの使用方法、最終アプリケーションの公開方法を学びます。
出力は、KotlinConf イベント専用の KotlinConf Explorer Web アプリで、会議の講演へのリンクが含まれます。ユーザーは、1 つのページですべての講演を視聴し、視聴済みまたは未視聴としてマークできます。
このチュートリアルでは、Kotlin の事前知識と HTML および CSS の基本的な知識があることを前提としています。React の背後にある基本的な概念を理解すると、サンプルコードの理解に役立つ場合がありますが、厳密には必須ではありません。
最終的なアプリケーションはこちらで入手できます。
開始する前に
-
最新バージョンの IntelliJ IDEA をダウンロードしてインストールします。
-
プロジェクトテンプレートを複製し、IntelliJ IDEA で開きます。このテンプレートには、必要なすべての構成と依存関係を備えた基本的な Kotlin Multiplatform Gradle プロジェクトが含まれています。
build.gradle.kts
ファイルの依存関係とタスク:
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")
}- このチュートリアルで使用する JavaScript コードを挿入するための
src/jsMain/resources/index.html
の HTML テンプレートページ:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello, Kotlin/JS!</title>
</head>
<body>
<div id="root"></div>
<script src="confexplorer.js"></script>
</body>
</html>Kotlin/JS プロジェクトは、コードとその依存関係すべてとともに、プロジェクトと同じ名前の単一の JavaScript ファイル
confexplorer.js
に自動的にバンドルされます。典型的なJavaScript の慣例として、本文の内容(root
div を含む)が最初にロードされ、ブラウザがスクリプトの前にすべてのページ要素をロードするようにします。
-
src/jsMain/kotlin/Main.kt
のコードスニペット:import kotlinx.browser.document
fun main() {
document.bgColor = "red"
}
開発サーバーを実行する
デフォルトでは、Kotlin Multiplatform Gradle プラグインには埋め込みの webpack-dev-server
のサポートが付属しており、サーバーを手動で設定しなくても IDE からアプリケーションを実行できます。
プログラムがブラウザで正常に実行されることをテストするには、IntelliJ IDEA 内の Gradle ツールウィンドウから run
または browserDevelopmentRun
タスク(other
または kotlin browser
ディレクトリで使用可能)を呼び出して、開発サーバーを起動します。

ターミナルからプログラムを実行するには、代わりに ./gradlew run
を使用します。
プロジェクトがコンパイルおよびバンドルされると、空白の赤いページがブラウザウィンドウに表示されます。

ホットリロード/継続モードを有効にする
変更を加えるたびにプロジェクトを手動でコンパイルして実行する必要がないように、継続コンパイル モードを構成します。続行する前に、実行中の開発サーバーインスタンスをすべて停止してください。
-
Gradle
run
タスクを最初に実行した後に IntelliJ IDEA が自動的に生成する実行構成を編集します。 -
Run/Debug Configurations ダイアログで、実行構成の引数に
--continuous
オプションを追加します。変更を適用すると、IntelliJ IDEA 内の Run ボタンを使用して、開発サーバーを再起動できます。ターミナルから継続的な Gradle ビルドを実行するには、代わりに
./gradlew run --continuous
を使用します。 -
この機能をテストするには、Gradle タスクの実行中に
Main.kt
ファイルでページの色を青に変更します。document.bgColor = "blue"
その後、プロジェクトが再コンパイルされ、リロード後にブラウザページが新しい色になります。
開発プロセス中は、開発サーバーを継続モードで実行し続けることができます。変更を加えると、ページが自動的に再構築およびリロードされます。
このプロジェクトの状態は、master
ブランチのこちらにあります。
Web アプリの下書きを作成する
React を使用して最初の静的ページを追加する
アプリに簡単なメッセージを表示するには、Main.kt
ファイルのコードを次のように置き換えます。
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!"
}
})
}
render()
関数は、kotlin-react-dom に、fragment 内の最初の HTML 要素をroot
要素にレンダリングするように指示します。この要素は、テンプレートに含まれていたsrc/jsMain/resources/index.html
で定義されたコンテナです。- コンテンツは
<h1>
ヘッダーで、typesafe DSL を使用して HTML をレンダリングします。 h1
はラムダパラメータを取る関数です。文字列リテラルの前に+
記号を追加すると、演算子のオーバーロードを使用してunaryPlus()
関数が実際に呼び出されます。囲まれた HTML 要素に文字列を追加します。
プロジェクトが再コンパイルされると、ブラウザに次の HTML ページが表示されます。

HTML を Kotlin の typesafe HTML DSL に変換する
React 用の Kotlin ラッパー には、純粋な Kotlin コードで HTML を記述できるようにするドメイン固有言語 (DSL)が付属しています。この点で、JavaScript の JSX に似ています。ただし、このマークアップは Kotlin であるため、オートコンプリートや型チェックなど、静的に型付けされた言語のすべての利点が得られます。
将来の Web アプリケーションの従来の HTML コードと、Kotlin の typesafe バリアントを比較します。
- HTML
- Kotlin
<h1>KotlinConf Explorer</h1>
<div>
<h3>Videos to watch</h3>
<p>
John Doe: Building and breaking things
</p>
<p>
Jane Smith: The development process
</p>
<p>
Matt Miller: The Web 7.0
</p>
<h3>Videos watched</h3>
<p>
Tom Jerry: Mouseless development
</p>
</div>
<div>
<h3>John Doe: Building and breaking things</h3>
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder"/>
</div>
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"
}
}
Kotlin コードをコピーして、main()
関数内の Fragment.create()
関数呼び出しを更新し、以前の h1
タグを置き換えます。
ブラウザがリロードされるまで待ちます。ページは次のようになります。

マークアップで Kotlin 構造を使用して動画を追加する
この DSL を使用して Kotlin で HTML を記述することには、いくつかの利点があります。ループ、条件、コレクション、文字列補間など、通常の Kotlin 構造を使用してアプリを操作できます。
動画のハードコードされたリストを Kotlin オブジェクトのリストに置き換えることができます。
-
Main.kt
で、すべての動画属性を 1 か所に保持するために、Video
data class を作成します。data class Video(
val id: Int,
val title: String,
val speaker: String,
val videoUrl: String
) -
2 つのリスト(未視聴の動画と視聴済みの動画)をそれぞれ入力します。これらの宣言を
Main.kt
のファイルレベルに追加します。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")
) -
これらの動画をページで使用するには、Kotlin の
for
ループを記述して、未視聴のVideo
オブジェクトのコレクションを反復処理します。「Videos to watch」の下にある 3 つのp
タグを次のスニペットに置き換えます。for (video in unwatchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
} -
同じプロセスを適用して、「Videos watched」に続く単一のタグのコードも変更します。
for (video in watchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
}
ブラウザがリロードされるまで待ちます。レイアウトは以前と同じままになります。ループが機能していることを確認するために、リストにさらに動画を追加できます。
typesafe CSS でスタイルを追加する
Emotion ライブラリ用の kotlin-emotion ラッパーを使用すると、CSS 属性(動的な属性を含む)を JavaScript を使用した HTML と並行して指定できます。概念的には、CSS-in-JS に似ていますが、Kotlin 用です。DSL を使用する利点は、Kotlin コード構造を使用してフォーマットルールを表現できることです。
このチュートリアルのテンプレートプロジェクトには、kotlin-emotion
の使用に必要な依存関係がすでに含まれています。
dependencies {
// ...
// Kotlin React Emotion (CSS) (chapter 3)
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion")
// ...
}
kotlin-emotion
を使用すると、スタイルを定義できる HTML 要素 div
および h3
内に css
ブロックを指定できます。
ビデオプレーヤーをページの右上隅に移動するには、CSS を使用して、ビデオプレーヤー(スニペットの最後の div
)のコードを調整します。
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"
}
}
他のスタイルも自由に試してみてください。たとえば、fontFamily
を変更したり、UI に color
を追加したりできます。
アプリコンポーネントを設計する
React の基本的な構成要素は components と呼ばれます。コンポーネント自体も、他のより小さなコンポーネントで構成できます。コンポーネントを組み合わせることで、アプリケーションを構築します。コンポーネントを汎用的で再利用できるように構造化すると、コードやロジックを複製せずに、アプリの複数の部分で使用できます。
render()
関数の内容は、通常、基本的なコンポーネントを記述します。アプリケーションの現在のレイアウトは次のようになります。

アプリケーションを個々のコンポーネントに分解すると、各コンポーネントがそれぞれの責任を処理する、より構造化されたレイアウトになります。

コンポーネントは特定の機能をカプセル化します。コンポーネントを使用すると、ソースコードが短くなり、読みやすく理解しやすくなります。
メインコンポーネントを追加する
アプリケーションの構造の作成を開始するには、最初に root
要素にレンダリングするメインコンポーネントである App
を明示的に指定します。
-
src/jsMain/kotlin
フォルダに新しいApp.kt
ファイルを作成します。 -
このファイル内に、次のスニペットを追加し、typesafe HTML を
Main.kt
からそのファイルに移動します。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<Props> {
// typesafe HTML goes here, starting with the first h1 tag!
}FC
関数は、function component を作成します。 -
Main.kt
ファイルで、main()
関数を次のように更新します。fun main() {
val container = document.getElementById("root") ?: error("Couldn't find root container!")
createRoot(container).render(App.create())
}これで、プログラムは
App
コンポーネントのインスタンスを作成し、指定されたコンテナにレンダリングします。
React の概念の詳細については、ドキュメントとガイドを参照してください。
リストコンポーネントを抽出する
watchedVideos
リストと unwatchedVideos
リストにはそれぞれ動画のリストが含まれているため、単一の再利用可能なコンポーネントを作成し、リストに表示されるコンテンツのみを調整するのが理にかなっています。
VideoList
コンポーネントは App
コンポーネントと同じパターンに従います。FC
ビルダー関数を使用し、unwatchedVideos
リストのコードが含まれています。
-
src/jsMain/kotlin
フォルダに新しいVideoList.kt
ファイルを作成し、次のコードを追加します。import kotlinx.browser.window
import react.*
import react.dom.*
import react.dom.html.ReactHTML.p
val VideoList = FC<Props> {
for (video in unwatchedVideos) {
p {
+"${video.speaker}: ${video.title}"
}
}
} -
App.kt
で、パラメータなしでVideoList
コンポーネントを呼び出して使用します。// . . .
div {
h3 {
+"Videos to watch"
}
VideoList()
h3 {
+"Videos watched"
}
VideoList()
}
// . . .今のところ、
App
コンポーネントはVideoList
コンポーネントによって表示されるコンテンツを制御できません。ハードコードされているため、同じリストが 2 回表示されます。
コンポーネント間でデータを渡すために props を追加する
VideoList
コンポーネントを再利用するため、さまざまなコンテンツを入力できるようにする必要があります。アイテムのリストを属性としてコンポーネントに渡す機能を追加できます。React では、これらの属性は props と呼ばれます。React でコンポーネントの props が変更されると、フレームワークはコンポーネントを自動的に再レンダリングします。
VideoList
の場合は、表示する動画のリストを含む prop が必要になります。VideoList
コンポーネントに渡すことができるすべての props を保持するインターフェースを定義します。
-
次の定義を
VideoList.kt
ファイルに追加します。external interface VideoListProps : Props {
var videos: List<Video>
}external 修飾子は、コンパイラにインターフェースの実装が外部から提供されることを伝え、宣言から JavaScript コードを生成しようとしません。
-
FC
ブロックにパラメータとして渡される props を使用するようにVideoList
のクラス定義を調整します。val VideoList = FC<VideoListProps> { props `->`
for (video in props.videos) {
p {
key = video.id.toString()
+"${video.speaker}: ${video.title}"
}
}
}key
属性は、props.videos
の値が変更された場合に React レンダラーが何を行うかを判断するのに役立ちます。キーを使用して、リストのどの部分を更新する必要があるか、どの部分を同じままにするかを判断します。React ガイドで、リストとキーの詳細を確認できます。 -
App
コンポーネントで、子コンポーネントが適切な属性でインスタンス化されていることを確認します。App.kt
で、h3
要素の下にある 2 つのループを、unwatchedVideos
とwatchedVideos
の属性とともにVideoList
の呼び出しに置き換えます。Kotlin DSL では、VideoList
コンポーネントに属するブロック内で属性を割り当てます。h3 {
+"Videos to watch"
}
VideoList {
videos = unwatchedVideos
}
h3 {
+"Videos watched"
}
VideoList {
videos = watchedVideos
}
リロード後、ブラウザにリストが正しくレンダリングされることが表示されます。
リストをインタラクティブにする
まず、ユーザーがリストエントリをクリックしたときにポップアップするアラートメッセージを追加します。VideoList.kt
で、現在選択されている動画でアラートをトリガーする onClick
ハンドラ関数を追加します。
// . . .
p {
key = video.id.toString()
onClick = {
window.alert("Clicked $video!")
}
+"${video.speaker}: ${video.title}"
}
// . . .
ブラウザウィンドウでリスト項目の 1 つをクリックすると、次のようなアラートウィンドウで動画に関する情報が表示されます。

ラムダとして直接 onClick
関数を定義することは簡潔で、プロトタイピングに非常に役立ちます。ただし、Kotlin/JS での等価性の現在の仕組みにより、パフォーマンスの観点からはクリックハンドラを渡す最適な方法ではありません。レンダリングパフォーマンスを最適化する場合は、関数を変数に格納して渡すことを検討してください。
値を保持するために状態を追加する
ユーザーにアラートを表示するだけでなく、選択した動画を ▶ 三角形で強調表示する機能を追加できます。そのためには、このコンポーネントに固有の state を導入します。
状態は、React の中心的な概念の 1 つです。最新の React(いわゆる Hooks API を使用)では、状態は useState
hook を使用して表されます。
-
次のコードを
VideoList
宣言の先頭に追加します。val VideoList = FC<VideoListProps> { props `->`
var selectedVideo: Video? by useState(null)
// . . .VideoList
関数型コンポーネントは状態を保持します(現在の関数呼び出しとは独立した値)。状態は null 許容で、Video?
型です。そのデフォルト値はnull
です。- React の
useState()
関数は、関数が複数回呼び出されても状態を追跡するようにフレームワークに指示します。たとえば、デフォルト値を指定した場合でも、React はデフォルト値が最初にのみ割り当てられるようにします。状態が変化すると、コンポーネントは新しい状態に基づいて再レンダリングされます。 by
キーワードは、useState()
が 委譲プロパティとして機能することを示します。他の変数と同様に、値を読み書きします。useState()
の背後にある実装は、状態を機能させるために必要なメカニズムを処理します。
State Hook の詳細については、React ドキュメントを参照してください。
-
VideoList
コンポーネントのonClick
ハンドラとテキストを次のように変更します。val VideoList = FC<VideoListProps> { props `->`
var selectedVideo: Video? by useState(null)
for (video in props.videos) {
p {
key = video.id.toString()
onClick = {
selectedVideo = video
}
if (video == selectedVideo) {
+"▶ "
}
+"${video.speaker}: ${video.title}"
}
}
}- ユーザーが動画をクリックすると、その値が
selectedVideo
変数に割り当てられます。 - 選択されたリストエントリがレンダリングされると、三角形が先頭に付加されます。
- ユーザーが動画をクリックすると、その値が
状態管理の詳細については、React FAQを参照してください。
ブラウザを確認し、リストのアイテムをクリックして、すべてが正しく動作していることを確認します。
コンポーネントを構成する
現在、2 つの動画リストは単独で動作しています。つまり、各リストは選択された動画を追跡します。 ユーザーは、1 つのプレーヤーしかない場合でも、未視聴リストと視聴済みリストの両方で 2 つの動画を選択できます。

リストは、リスト内と兄弟リスト内の両方で、どの動画が選択されているかを追跡できません。その理由は、選択された動画が リスト 状態の一部ではなく、アプリケーション 状態の一部であるためです。これは、状態を個々のコンポーネントから リフト する必要があることを意味します。
状態をリフトする
React は、props が親コンポーネントからその子コンポーネントにのみ渡されるようにします。これにより、コンポーネントがハードワイヤで接続されるのを防ぎます。
コンポーネントが兄弟コンポーネントの状態を変更する場合は、親を介して行う必要があります。その時点で、状態は子コンポーネントのいずれにも属さなくなり、包括的な親コンポーネントに属するようになります。
コンポーネントから親への状態の移行プロセスは、状態のリフト と呼ばれます。アプリの場合は、currentVideo
を状態として App
コンポーネントに追加します。
-
App.kt
で、App
コンポーネントの定義の先頭に次を追加します。val App = FC<Props> {
var currentVideo: Video? by useState(null)
// . . .
}VideoList
コンポーネントは、状態を追跡する必要がなくなりました。代わりに、現在の動画を prop として受け取ります。 -
VideoList.kt
でuseState()
呼び出しを削除します。 -
選択された動画を prop として受け取るように
VideoList
コンポーネントを準備します。そのためには、VideoListProps
インターフェースを展開してselectedVideo
を含めます。external interface VideoListProps : Props {
var videos: List<Video>
var selectedVideo: Video?
} -
三角形の条件を、
state
の代わりにprops
を使用するように変更します。if (video == props.selectedVideo) {
+"▶ "
}
ハンドラを渡す
現時点では、props に値を割り当てる方法がないため、onClick
関数は現在設定されている方法では機能しません。親コンポーネントの状態を変更するには、状態を再度リフトする必要があります。
React では、状態は常に親から子に流れます。したがって、子コンポーネントの 1 つから アプリケーション 状態を変更するには、ユーザーインタラクションを処理するためのロジックを親コンポーネントに移動し、ロジックを prop として渡す必要があります。Kotlin では、変数に 関数の型を持たせることができることに注意してください。
-
VideoListProps
インターフェースを再度展開して、Video
を受け取りUnit
を返す関数である変数onSelectVideo
を含めます。external interface VideoListProps : Props {
// ...
var onSelectVideo: (Video) `->` Unit
} -
VideoList
コンポーネントで、onClick
ハンドラで新しい prop を使用します。onClick = {
props.onSelectVideo(video)
}これで、
VideoList
コンポーネントからselectedVideo
変数を削除できます。 -
App
コンポーネントに戻り、2 つの動画リストそれぞれに対してselectedVideo
とonSelectVideo
のハンドラを渡します。VideoList {
videos = unwatchedVideos // および watchedVideos それぞれ
selectedVideo = currentVideo
onSelectVideo = { video `->`
currentVideo = video
}
} -
視聴済みの動画リストに対して前の手順を繰り返します。
ブラウザに戻り、動画を選択したときに、重複することなく 2 つのリスト間で選択がジャンプすることを確認します。
コンポーネントをさらに追加する
ビデオプレーヤーコンポーネントを抽出する
自己完結型の別のコンポーネント(現在プレースホルダー画像であるビデオプレーヤー)を作成できます。ビデオプレーヤーは、講演のタイトル、講演の作成者、および動画へのリンクを知る必要があります。この情報はすでに各 Video
オブジェクトに含まれているため、prop として渡して、その属性にアクセスできます。
-
新しい
VideoPlayer.kt
ファイルを作成し、VideoPlayer
コンポーネントに次の実装を追加します。import csstype.*
import react.*
import emotion.react.css
import react.dom.html.ReactHTML.button
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h3
import react.dom.html.ReactHTML.img
external interface VideoPlayerProps : Props {
var video: Video
}
val VideoPlayer = FC<VideoPlayerProps> { props `->`
div {
css {
position = Position.absolute
top = 10.px
right = 10.px
}
h3 {
+"${props.video.speaker}: ${props.video.title}"
}
img {
src = "https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder"
}
}
} -
VideoPlayerProps
インターフェースはVideoPlayer
コンポーネントが null 許容でないVideo
を取ることを指定しているため、App
コンポーネントでこれに確実に対応してください。App.kt
で、ビデオプレーヤーの以前のdiv
スニペットを次のように置き換えます。currentVideo?.let { curr `->`
VideoPlayer {
video = curr
}
}let
スコープ関数 は、VideoPlayer
コンポーネントがstate.currentVideo
が null でない場合にのみ追加されるようにします。
これで、リストのエントリをクリックすると、ビデオプレーヤーが起動し、クリックされたエントリの情報が入力されます。
ボタンを追加してワイヤリングする
ユーザーが動画を視聴済みまたは未視聴としてマークし、2 つのリスト間で移動できるようにするには、VideoPlayer
コンポーネントにボタンを追加します。
このボタンは 2 つの異なるリスト間で動画を移動するため、状態の変更を処理するロジックを VideoPlayer
から リフト して、prop として親から渡す必要があります。ボタンは、動画が視聴されたかどうかに基づいて異なって表示される必要があります。これも prop として渡す必要がある情報です。
-
VideoPlayerProps
インターフェースをVideoPlayer.kt
で展開して、それらの 2 つのケースのプロパティを含めます。external interface VideoPlayerProps : Props {
var video: Video
var onWatchedButtonPressed: (Video) `->` Unit
var unwatchedVideo: Boolean
} -
これで、ボタンを実際のコンポーネントに追加できます。次のスニペットを
VideoPlayer
コンポーネントの本体に、h3
タグとimg
タグの間に追加します。button {
css {
display = Display.block
backgroundColor = if (props.unwatchedVideo) NamedColor.lightgreen else NamedColor.red
}
onClick = {
props.onWatchedButtonPressed(props.video)
}
if (props.unwatchedVideo) {
+"Mark as watched"
} else {
+"Mark as unwatched"
}
}スタイルを動的に変更できる Kotlin CSS DSL の助けを借りて、基本的な Kotlin
if
式を使用してボタンの色を変更できます。