Kotlin/JS 리액트 맛보기

코틀린 너무 좋아 코틀린으로 샤워하고 코틀린에 밥말아먹고 코틀린으로 이닦아야지

이번에 해볼 것은 코틀린으로 코드를 작성하여 자바스크립트로 컴파일을 하는 Kotlin/JS를 이용하여 리액트를 써보는 것입니다. 이거 이용하면 스프링부트 백엔드 + 리액트 프론트엔드를 코틀린이라는 언어 하나로 작성할 수 있지 않을까! 생각중인데 아무래도 하다보면 프론트엔드쪽에서 뻑이날것같다.

코틀린은 자바를 100%… 커버할… 수 있다고…는 하는데… 자바스크립트는 그게 아닐것같으니…

코틀린이다보니 IntelliJ를 쓰는게 마음이 편할 것이다. 신규 프로젝트 > 코틀린 > Gradle 로 선택해서 대충 프로젝트를 만든다.

참고로 이 부분은 Kotlin/JS 공식 문서를 참고했다.

프로젝트 설정

그 다음 우리는 이 프로젝트에서 JVM을 쓰지 않을 것이므로 build.gradle.kts 를 열어서 관련 부분을 모두 지운다.

plugins {
    // 이 플러그인 삭제
    kotlin("jvm") version "1.9.23"
}

group = "me.aosamesan"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

// 이 아래 삭제
dependencies {
    testImplementation(kotlin("test"))
}

tasks.test {
    useJUnitPlatform()
}
kotlin {
    jvmToolchain(21)
}

그래서 최종적으로는 다음과 같이 바꾼다.

plugins {
    kotlin("multiplatform") version "2.0.0"
}

group = "me.aosamesan"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

fun kotlinw(target: String, version: String) = "org.jetbrains.kotlin-wrappers:kotlin-$target:$version"

kotlin {
    js {
        binaries.executable()
        browser {
            commonWebpackConfig {
                cssSupport { enabled = true }
                scssSupport { enabled = true }
            }
        }
    }
    sourceSets {
        jsMain {
            dependencies {
                implementation(kotlinw("react", "18.3.1-pre.751"))
                implementation(kotlinw("react-dom", "18.3.1-pre.751"))
            }
        }
        commonTest {
            dependencies {
                implementation(kotlin("test"))
            }
        }
    }
}

kotlin.js.browser.commonWebpackConfig 은 웹팩 설정이다. cssSupport나 scssSupport를 켜놓으면 jsMain.resources에 있는 css, scss, sass를 사용할 수 있게 해준다. 하지만 이걸 그냥 사용할 수는 없다… 이건 나중에..

jsMain의 dependencies에 리액트와 리액트돔이 추가되어있다. Kotlin/JS는 external로 자바스크립트의 것들을 가져와서 사용하는 것 같은데 (C#에서의 그것과 비슷한듯?) npm으로 직접 사용해서 external을 일일이 지정하는건 매우 귀찮으므로 래퍼 라이브러리가 따로 있다. 이건 여기 참고.

sourceSets에 jsMain과 commonTest라는게 있다. jsMain은 멀티플랫폼에서 자바스크립트의 소스 루트가 되는 이름이다. 이것도 이따가 나온다. commonTest는 모든 플랫폼에 공통적으로 사용하는 것이고, implementation(kotlin(“test”))는 플랫폼에 맞춰서 적당한 테스트를 제공해준다고 한다..

그 다음 src/main과 src/test를 지운다. main/test는 jvm을 타겟으로 할 때나 사용하므로 지운다. 참고로 sourceSets가 설정되지 않은 상태에서 main/test를 지우려고 하면 한번에 안지워진다.

그다음 src 우클릭 -> 새로만들기 -> 경로를 선택한다.

영어는 대충 New / Directory일듯? 그러면 다음과 같은 메뉴가 나온다. 여기서 jsMain과 jsTest를 만든다.

아래쪽 4개를 다 누르면 된다.

index.html과 스타일시트 추가

index.html이나 스타일시트는 jsMain.resources 에 추가하면 된다.

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8" />
    <title>Kotlin/JS React</title>
</head>
<body>
<div id="root"></div>
<script src="./kotlinjs-react.js"></script>
</body>
</html>

번들된 스크립트 이름은 웹팩설정에서 따로 설정하지 않으면 모듈이름(프로젝트이름)을 사용하면 된다.

css/test.scss

.test-component {
 & .test-text {
  color: red;
 }
}

Sass 테스트용으로 넣을 스타일이다.

Main.kt

리액트의 진입점 처럼 main함수에 쓴다고 보면 된다. 맛보기이므로 컴포넌트도 그냥 한 파일에 다 넣어놨다.

import react.FC
import react.Props
import react.create
import react.dom.client.createRoot
import react.dom.html.ReactHTML.button
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h1
import react.dom.html.ReactHTML.p
import react.useState
import web.cssom.ClassName
import web.dom.document

external fun require(name: String): dynamic

fun main() {
    require("./css/test.scss")
    createRoot(document.getElementById("root")!!).render(App.create())
}

external interface CountProps: Props {
    var count: Int
}

val App = FC {
    h1 {
        +"React from Kotlin"
    }
    p {
        className = ClassName("test-text")
        +"foo"
    }
    div {
        className = ClassName("test-component")
        p {
            className = ClassName("test-text")
            +"bar"
        }
    }
    Counter {
        count = 0
    }
}

val Counter = FC<CountProps> { props ->
    val (count, setCount) = useState(props.count)

    h1 {
        +"Count : $count"
    }
    div {
        button {
            +"Increase"
            onClick = { setCount(count + 1) }
        }
        button {
            +"Decrease"
            onClick = { setCount(count - 1) }
        }
    }
}

foo, bar는 스타일 시트가 잘 먹나 확인하려고 일부러 넣은 것이다. 그리고 css/scss 파일을 import할 때에는 require를 써야하는데, 얘는 코틀린의 함수가 아니므로 external fun으로 되어있다. 그리고 자바스크립트는동적타입? 언어이고 require가 뭘 가져올지 모르므로 dynamic이 반환형이다. 와 C#에서 보던걸 여기서도 보네.

그리고 CountProps를 만드려고 Props를 상속받는데, Props가 external 이라 CountProps도 external이어야 한다는 듯?

Counter 는 그냥 간단한 숫자 더하기빼기 컴포넌트다. 사실 JS 리액트에서도 간단한 테스트용으로 만들던거라 딱히 설명은 필요 없을 듯. 근데 코틀린이라서 그런지 이벤트 핸들러가 매우 간결? 하다

실행은 jsBrowserDevelopmentRun 으로 실행하면 된다.

./gradlew jsBrowserDevelopmentRun

혹은 IntelliJ Gradle 메뉴에 있는걸 더블클릭 하면 된다.

그러면 브라우저가 실행되서 다음과 같이 뜨고

기대했던대로 bar에만 color:red 가 먹는 것이나 Counter 컴포넌트도 제대로 작동하는 것을 확인할 수 있다.

코틀린 래퍼에는 MUI나 리덕스, 라우터돔 등등도 있어서 다음에는 스프링부트백엔드+리액트프론트엔드 개삽질해서 오겠음

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다