Kotlin/JS 상태관리 2

어제 쓴 게 좀 그래서 좀 더 살펴보니 useReducer라는게 있었지 참! 싶어서 그걸로 고쳤다.

package utils

import react.*

external interface ReducerAction<TActionType> where TActionType : Enum<TActionType> {
    var type: TActionType
    var value: Any?
}

abstract class UseReducer<TState, TActionType>(private val initialState: TState) where TState : State, TActionType : Enum<TActionType> {
    private val reducerContext = createContext<ReducerInstance<TState, ReducerAction<TActionType>>>()

    val provider = FC<PropsWithChildren> { props ->
        reducerContext.Provider {
            value = useReducer(this@UseReducer::reduce, initialState)
            +props.children
        }
    }

    protected abstract fun reduce(state: TState, action: ReducerAction<TActionType>): TState

    fun useReducerInstance(): ReducerInstance<TState, ReducerAction<TActionType>> {
        return requireNotNull(useContext(reducerContext)) {
            "Context is not initialized"
        }
    }

    fun useState(): TState = requireNotNull(useContext(reducerContext)).component1()

    fun <U> useSelector(selector: (TState) -> U): U = requireNotNull(useContext(reducerContext)) {
        "Context is not initialized"
    }.component1().let(selector)

    fun useDispatch(): Dispatch<ReducerAction<TActionType>> = requireNotNull(useContext(reducerContext)) {
        "Context is not initialized"
    }.component2()
}

처음에 ReducerAction을 data class 로 했었는데 에러가 나서 external interface로 바꿨다. data class를 자바스크립트로 트랜스파일 했을 때 문제가 일어나는 듯 하다.

package states

import js.objects.jso
import react.State
import utils.ReducerAction
import utils.UseReducer

external interface CounterState : State {
    var count: Int
}

enum class CounterStateActionType {
    INCREASE,
    DECREASE,
    ADD,
    SUBTRACT
}


object Counter : UseReducer<CounterState, CounterStateActionType>(jso { count = 0 }) {
    override fun reduce(state: CounterState, action: ReducerAction<CounterStateActionType>): CounterState = when (action.type) {
        CounterStateActionType.INCREASE -> jso { count = state.count + 1 }
        CounterStateActionType.DECREASE -> jso { count = state.count - 1 }
        CounterStateActionType.ADD -> jso {
            count = state.count + checkNotNull(action.value as Int) {
                "value is null"
            }
        }
        CounterStateActionType.SUBTRACT -> jso {
            count = state.count - checkNotNull(action.value as Int) {
                "value is null"
            }
        }
    }
}
import js.objects.jso
import react.FC
import react.create
import react.dom.client.createRoot
import react.dom.html.ReactHTML.button
import react.dom.html.ReactHTML.h1
import states.CounterStateActionType
import states.Counter
import web.dom.document

fun main() {
    val root = document.getElementById("root")!!
    createRoot(root).render(App.create())
}

val App = FC {
    Counter.provider {
        CountViewer()
        Buttons()
    }
}

val CountViewer = FC {
    val count = Counter.useSelector { it.count }

    h1 {
        +"Count : $count"
    }
}

val Buttons = FC {
    val dispatch = Counter.useDispatch()

    button {
        onClick = {
            dispatch(jso {
                type = CounterStateActionType.INCREASE
            })
        }
        +"Increase"
    }
    button {
        onClick = {
            dispatch(jso {
                type = CounterStateActionType.DECREASE
            })
        }
        +"Decrease"
    }
    button {
        onClick = {
            dispatch(jso {
                type = CounterStateActionType.ADD
                value = 5
            })
        }
        +"Add 5"
    }
    button {
        onClick = {
            dispatch(jso {
                type = CounterStateActionType.SUBTRACT
                value = 5
            })
        }
        +"Subtract 5"
    }
}

답글 남기기

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