2로 올린걸 사용하려는데 시발 제대로 작동 안하는 경우가 있었다. 그래서 그냥 다음과 같이 전역 메서드로만 구성하는게 해답일 것 같다.
import js.array.JsTuple2
import js.array.tupleOf
import react.*
typealias SuspendDispatch<TAction> = suspend (action: TAction) -> Unit
typealias SuspendReducerInstance<TState, TAction> = JsTuple2<TState, SuspendDispatch<TAction>>
typealias Middleware<TState, TActionType> = (state: TState, action: ReducerAction<TActionType>) -> TState
external interface ReducerAction<TActionType : Enum<TActionType>> {
var type: TActionType
var payload: Any?
}
fun <TState : State, TActionType : Enum<TActionType>> createContext(): RequiredContext<SuspendReducerInstance<TState, ReducerAction<TActionType>>> =
createRequiredContext()
fun <TState : State, TActionType : Enum<TActionType>> RequiredContext<SuspendReducerInstance<TState, ReducerAction<TActionType>>>.useState() =
useRequiredContext(this).component1()
fun <TState : State, TActionType : Enum<TActionType>, U> RequiredContext<SuspendReducerInstance<TState, ReducerAction<TActionType>>>.useSelector(
selector: (TState) -> U
): U =
useState().let(selector)
fun <TState : State, TActionType : Enum<TActionType>> RequiredContext<SuspendReducerInstance<TState, ReducerAction<TActionType>>>.useDispatch() =
useRequiredContext(this).component2()
fun <TState : State, TActionType : Enum<TActionType>> useReducer(
initialState: TState,
reducer: Reducer<TState, ReducerAction<TActionType>>,
vararg middlewares: Middleware<TState, TActionType>
): SuspendReducerInstance<TState, ReducerAction<TActionType>> {
val targetReducer: Reducer<TState, ReducerAction<TActionType>> = { state, action ->
reducer(middlewares.fold(state) { accumulator, middleware ->
middleware(accumulator, action)
}, action)
}
val (state, syncDispatch) = useReducer(targetReducer, initialState)
return tupleOf(state) { action ->
syncDispatch(action)
}
}
미들웨어는 리덕스 미들웨어처럼 다음 상태도 알 수 있는 건 아니고(store를 구현한게 아니라서;) 그냥 받은 액션과 현재 상태만 로깅하는 용도다.
예시 카운터
import js.objects.jso
import react.State
external interface CounterState : State {
var count: Int
}
enum class CounterAction {
INCREMENT,
DECREMENT,
ADD,
SUBTRACT
}
val CounterContext = createContext<CounterState, CounterAction>()
@Suppress("FunctionName")
fun CounterReducer(state: CounterState, action: ReducerAction<CounterAction>): CounterState {
return when (action.type) {
CounterAction.INCREMENT -> jso {
count = state.count + 1
}
CounterAction.DECREMENT -> jso {
count = state.count - 1
}
CounterAction.ADD -> jso {
count = state.count + requireNotNull(action.payload as? Int)
}
CounterAction.SUBTRACT -> jso {
count = state.count - requireNotNull(action.payload as? Int)
}
}
}
사용법은 CounterContext.Provider 로 적절히 감싸고 CounterContext.useSelector나 Countercontext.useDispatch 를 쓰면 된다.
다음에 알아볼 것은 npm 라이브러리를 사용할 때 dynamic 천지면 매우 불편하므로 어느정도 래핑을 해야하는데 거기서 기억해야할 것이다.
가끔 라이브러리들을 보면 클래스로 정의 -> 인스턴스 생성한걸 export
하는 경우가 있다. 그리고 아래처럼 interface에 함수들을 정의해서 인스턴스가 함수처럼 작동할 수 있게 하는 경우가 있다.
예 : notistack #Link
이걸 코틀린으로 가져올 때 하나 간과한게 있었다.
함수 호출은 기본적으로 call 로 하는데, call의 맨 앞 파라미터는 인스턴스의 메서드라면 this, 정적 메서드라면 null이다. (null인지 undefined인지는 사실 모름)
그러므로 가져올 때는 this가 무조건 있도록 해야한다.
kotlin에서는 definedExternally 를 쓰면 대충 JS에서는 정의되어 있으니 그렇게 알고 있어 정도다.
끗.
답글 남기기