#概要
初投稿です。
Kotlin/JS×Reactを少し勉強してみたので、メモ書きとして残しておきます。
#Kotlin/JSとは
Kotlinで書いたコードをJavaScriptに変換してくれるものです。もちろんフロントエンドとバックエンド、どちらも対応できます。
今回はこのKotlin/JSに、フロントエンドでおなじみのReactを掛け合わせたものを使ってみます。
ちなみにクラス型、関数型(Hooks)、どちらも使用可能ですが、ここではクラス型で記述しています。
#準備
Kotlin/JS×Reactは
1.npm(yarn)
を使う方法
2.Gradle
を使う方法
とあります。Kotlin/JVM
ではGradleを使うため、これが使えるのは大きいです。
しかし、npmの方が簡単に試すことが出来るので、今回はこちらで書いていこうと思います。
まずはサンプルのコードを用意します。
コードを保存したい場所をカレントディレクトリにして、こちらのコマンドを叩きます。
$ npm install -g create-react-kotlin-app
$ create-react-kotlin-app my-app
これらを叩くことで、myapp
という名前のサンプルアプリが作成されます。
それでは実際に動かしてみましょう。
$ cd my-app
$ npm start
すると、KotlinとReactのロゴマークがくるくる回る画面が表示されると思います。
されない場合は、コマンドラインに書かれたURLの方にアクセスしてみてください。
ではこちらを解説・・・していきたいところですが、何も知らずに見ると理解するのが困難になります。
ですので、まずは簡単なところから固めていきましょう。
こちらをcloneしてください
$ git clone https://github.com/Wansuko-cmd/kotlinjs-react-first
これは、先ほどのサンプルアプリからいろいろと省いたものになります。
こちらを同じように動かしてみましょう。
$ npm install
$ npm start
実行すると、Hello World!!
の文字が真ん中に表示されると思います。
まずはこのアプリから見ていきましょう。
#Hello World!!
それでは初めにindex.html
を見てみます。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React Kotlin App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
こちらがnpm start
を実行したときに表示されるHTMLとなります。
id名がrootのところにkotlinで書いたコードが入る感じになります。
BootStrapといったもののCDN等を加えたいときは、ここに書くと導入することが出来ます。
次はindex.kt
を見てみます
package index
import app.*
import kotlinext.js.*
import react.dom.*
import kotlinx.browser.*
fun main(args: Array<String>) {
//src下のcssファイルをすべて読み込む
requireAll(require.context("src", true, js("/\\.css$/")))
render(document.getElementById("root")) {
app()
}
}
ここにあるmain関数が、npm start
実行時に走る関数です。
まずrequireAll
によってsrc下のcssファイルがすべて読み込まれます。
これを書かないと、cssファイルが適用されないので要注意です。
次にrender関数です。この関数はReactのものと同じで、ここのネストに表示させたいものを書いていく感じとなります。
今回の場合は、app()
、つまり関数app()の返り値を表示するようになっています。
ですので、次にApp.kt
を見てみましょう
package app
import react.*
class App : RComponent<RProps, RState>() {
override fun RBuilder.render() {
+"Hello World!!"
}
}
fun RBuilder.app() = child(App::class) {}
まずAppクラスの方から見ていきます。
このクラスはRComponent
を継承しています。
このRComponent
は、Reactでいうコンポーネントを作る際に継承する必要があります。
また、render関数を書くときにはRBuilder.render()
をオーバーライドする必要があります。
render関数の中身を見ると、文字列の先頭に+
が書かれています。
これはReactと違うところで、表示させたいところはこのように、先頭に+を書く必要があります。
今回の場合、「Hello World!!」を表示させるためにこのようになっています。
ちなみに、もしここに変数を入れたい場合は以下のようにします
//変数名がtextとすると
override fun RBuilder.render(){
+text //変数のみ表示させる場合
+"Hello ${text}" //文字列
}
kotlinですので、このような書き方が出来るわけですね。
それでは、app関数の方を見てみます。
これはRBuilder
の拡張関数として定義されています。
そして、この拡張関数の中に、先ほど見たAppクラスのインスタンスを入れています。
この拡張関数の記述によって、単にApp()
としただけで、Appクラスの中身を表示できるようになります。
以上がこのアプリの解説となります。
#HTMLタグについて
もちろんですが、Kotlin/JSでもHTMLタグを利用することが出来ます。
せっかくなので、先ほどの「Hello World!!」をh1タグで囲ってみましょう。
以下のコードを見てください。
package app
import react.*
import react.dom.*
class App : RComponent<RProps, RState>() {
override fun RBuilder.render() {
h1{
+"Hello World!!"
}
}
}
fun RBuilder.app() = child(App::class) {}
先ほどまでと違い、「Hello World!!」がh1に囲まれています。このように書くことで、HTMLタグのh1に囲まれている状況にできます。
もちろんh1だけでなく、様々なタグを使うことが出来ます。
また、以下のようにしてタグにクラス名をつけることもできます。
override fun RBuilder.render() {
div(classes = "text"){
h1{
+"Hello World!!"
}
}
}
#Stateについて
このようなアプリを組んでいると、コンポーネントに値を保存しておきたいということが良くあります。
Reactではこのような時Stateを使います。
もちろんKotlin/JSにおいてもStateを使うことが出来ます。
そこで、先ほど見ていったコードをいじって、実装してみましょう。
いじる場所は、App.kt
です。以下のように書き換えてください。
package app
import react.*
interface AppState: RState{
var name: String
}
class App : RComponent<RProps, AppState>() {
override fun AppState.init(){
name = "太郎"
}
override fun RBuilder.render() {
+"Hello ${state.name}"
}
}
fun RBuilder.app() = child(App::class) {}
これで実行すると、「Hello 太郎」という文字が見れると思います。
では解説していきます。
まず、インターフェイスで定義されているAppState
です。
Stateを使う際、まずRState
を継承したインターフェイスを定義してやる必要があります。
この中には、保存しておきたい変数を書いておきましょう
次にAppクラスです。
よく見るとRComponent<RProps, AppState>(){}
となっており、いままでRState
が入っていた場所にAppState
が入る形となっています。
このようにすることで、このコンポーネントがどのStateを使うかを宣言します。
Appクラスの中身も見てみましょう。
新しい記述が増えています
override fun AppState.init(){
name = "太郎"
}
AppStateの拡張関数であるinit()
をオーバーライドしています。
名前から分かるように、これはStateの初期化をするために使われます。
今回の場合、Stateの中に「太郎」の文字を入れています。
最後に、render関数にて、先ほどまで「Hello World!!」を入れていたところに、Stateで保存している変数を反映させています。
保存している変数を使うには、state.変数名
と書きます。
このコードの解説は以上となります。
ちなみにですが、Stateの値はsetStateを使うことで変えることが出来ます。App.ktを以下のように書き換えてください。
package app
import kotlinx.html.js.onClickFunction
import react.*
import react.dom.button
interface AppState: RState{
var name: String
}
class App : RComponent<RProps, AppState>() {
override fun AppState.init(){
name = "太郎"
}
override fun RBuilder.render() {
button {
+"Click"
attrs.onClickFunction = {
setState { name = "花子" }
}
}
+"Hello ${state.name}"
}
}
fun RBuilder.app() = child(App::class) {}
実行してみると、「Click」と書かれたボタンが表示されて、これを押すと「太郎」から「花子」になります。
変更部分はrender関数で、新たにボタンのHTMLタグが追加されています。
中ではボタンを押されたときの処理が書いてます。このように何かしらのイベントの処理が書きたいときはattrs
で書くことが出来ます。
そして中にはsetState
と書かれた処理があります。この中で変更処理を書けばState内に代入されている値を変更できます。
以上がStateの説明です。
#Propsについて
コンポーネント間で値を受け渡しをする際にはPropsを利用します。
こちらももちろんKotlin/JSで書くことが出来ます。
それでは実際に書いてみましょう。
今回も、先ほどいじっていたコードを同じようにいじっていきます。
具体的には、index.kt
からApp.kt
にPropsを渡して、それを表示する機構を作っていきたいと思います。
まずはApp.kt
からです。以下のように変更して下さい。
package app
import react.*
interface AppProps: RProps{
var name: String
}
class App : RComponent<AppProps, RState>() {
override fun RBuilder.render() {
+"Hello ${props.name}"
}
}
fun RBuilder.app(name: String) = child(App::class) {
attrs.name = name
}
それでは順番に見ていきましょう。
まずPropsで渡したい変数をインターフェイスで定義しています。この時、RProps
を継承する必要があります。
Appクラスでは先ほどとは反対側のRPropsのところを、定義したインターフェイスの名前に書き替えます。
このようにすることで、クラス内でPropsを利用することが出来ます。render関数の中身にあるように、「props.変数名」で使えます。
先ほどのStateと違い、Propsでは拡張関数の方にも手を出す必要があります。
このように書くことで、拡張関数に渡した引数をそのままPropsに入れることが出来ます。
次にindex.kt
です。以下のように書き換えてください。
package index
import app.*
import kotlinext.js.*
import react.dom.*
import kotlinx.browser.*
fun main(args: Array<String>) {
//src下のcssファイルをすべて読み込む
requireAll(require.context("src", true, js("/\\.css$/")))
//render関数
render(document.getElementById("root")) {
app("花子")
}
}
先ほどまで引数のなかったapp()
に引数として「花子」を渡しています。
これは拡張関数を定義した際に、引数を取るようにしたからです。ここで渡した引数は、先ほど指定したようにPropsに代入されます。
ちなみに、Propsの書き方は他にいくつかあります。詳しくは下のREADMEを読んでください。途中の部分に書いてあります。
https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-react/README.md
#サンプルコードを読む
ここまでの知識を使えば、最初に登場したサンプルコードが読めると思うので見ていきましょう。
まずindex.ktですが、先ほどまでのサンプルとほぼ同じですのでここでは省略します。
ですのでApp.kt
から見てみましょう。以下のようになっています。
package app
import react.*
import react.dom.*
import logo.*
import ticker.*
class App : RComponent<RProps, RState>() {
override fun RBuilder.render() {
div("App-header") {
logo()
h2 {
+"Welcome to React with Kotlin"
}
}
p("App-intro") {
+"To get started, edit "
code { +"app/App.kt" }
+" and save to reload."
}
p("App-ticker") {
ticker()
}
}
}
fun RBuilder.app() = child(App::class) {}
render関数を見ていると、コンポーネントが二つほど(logoとticker)出てきていることが分かります。あとはdivやh2などで囲っているだけとなります。
ここではStateもPropsも登場してませんね。
ではlogo.kt
を見てみましょう。これは中央でクルクル回っているロゴを出力するものです。
package logo
import react.*
import react.dom.*
@JsModule("src/logo/react.svg")
external val reactLogo: dynamic
@JsModule("src/logo/kotlin.svg")
external val kotlinLogo: dynamic
fun RBuilder.logo(height: Int = 100) {
div("Logo") {
attrs.jsStyle.height = height
img(alt = "React logo.logo", src = reactLogo, classes = "Logo-react") {}
img(alt = "Kotlin logo.logo", src = kotlinLogo, classes = "Logo-kotlin") {}
}
}
さらっと新しいものが出ていますね・・・
このJsModuleのアノテーションやdynamic型は、JavaScriptのライブラリ等をKotlin/JSでも使いまわせるようにするために用意されたもののようです。
この下の記事が分かりやすいので、こちらを参照してください(丸投げ)
https://sakebook.hatenablog.com/entry/2020/02/18/074814
このlogo.kt
は、クラスを書いておらず、拡張関数のみ書いています。このように書くことも可能です。
中身はクラスの時のrender関数の中身と同じような感じです。
途中に
attrs.jsStyle.height = height
というのがありますが、これは親要素であるdivの高さを指定しています。heightは拡張関数の中でデフォルト変数として指定されているので、この場合高さは100ということになります。
またimgの属性は、この例のように書くことでつけることが出来ます。
img(alt = "React logo.logo", src = reactLogo, classes = "Logo-react") {}
先ほどのクラス名をつける方法とほぼほぼ同じですね
最後にticker.kt
を見ていきましょう。
こちらは、ページを開けてからの時間をカウントする部分です。
package ticker
import react.*
import kotlinx.browser.*
interface TickerProps : RProps {
var startFrom: Int
}
interface TickerState : RState {
var secondsElapsed: Int
}
class Ticker(props: TickerProps) : RComponent<TickerProps, TickerState>(props) {
override fun TickerState.init(props: TickerProps) {
secondsElapsed = props.startFrom
}
var timerID: Int? = null
override fun componentDidMount() {
timerID = window.setInterval({
// actually, the operation is performed on a state's copy, so it stays effectively immutable
setState { secondsElapsed += 1 }
}, 1000)
}
override fun componentWillUnmount() {
window.clearInterval(timerID!!)
}
override fun RBuilder.render() {
+"This app has been running for ${state.secondsElapsed} seconds."
}
}
fun RBuilder.ticker(startFrom: Int = 0) = child(Ticker::class) {
attrs.startFrom = startFrom
}
ここではStateとPropsをフル活用しています。では見ていきましょう。
まずTickerPropsとしてstartFormを変数に持つProps、TickerStateとしてsecondsElapsedを変数に持つStateを定義しています。
次にTickerクラスです。ここではまずStateの変数を初期化しています。この際、Propsに格納されていた変数をStateに代入しています。
その後、何やら二つほど関数がオーバーライドされています。こちらはReactにもあるcomponentDidMount()
とcomponentWillUnmount()
を再現したものとなっています。ここで、1秒ごとにStateのsecondsElapsed
に1を足す処理を書いています。
ここで出てきたcomponentDidMount()
とcomponentWillUnmount()
等々はこの下のリンク先が詳しく説明しています。JSXの方を説明していますが、全然問題ないはずです。ぜひ参照してください。
https://blog.ikappio.com/drawing-with-setinterval-on-react/
あとはrender関数に、秒数を表示できるように変数+文章をぶち込んでいるだけです。
下の拡張関数も単にPropsの初期化を行っているだけとなります。
以上が説明となります。
#最後に
アプリの説明が結構荒い感じになっており申し訳ございませんorz
とりあえずこれらを使えば試しに使ってみるくらいのことはできると思います。
よりやってみたいという方はGitにある公式のREADME.mdを読んでみてもいいかもしれません。
https://github.com/JetBrains/kotlin-wrappers
ここではRouter等の外にもcssなども解説しています。
実はKotlin/JSではcssもKotlinで書けてしまいます。それについて解説しているわけです。
私のようにどうしても英語がダメな人は、私が勉強中にメモしたやつを貼っておきますので、そちらを参照してみて下さい。
https://github.com/Wansuko-cmd/kotlin-js-with-react-study/blob/master/README.md
また、冒頭でも説明した通り、Kotlin/JSでは関数型も対応しています。もちろんHooksも使えます。それについても公式のやつが参考になります。
https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-react/README.md
これで以上となります。間違い等ありましたらコメントしていただけると嬉しいです。
ありがとうございました。