首页 > 解决方案 > 如何在 kotlin-react useEffect 中订阅 StateFlow

问题描述

我正在尝试为 kotlin-react with functionalComponentkotlin创建一个小的反例1.4-M2。该示例应使用kotlinx.coroutines.flow. 我正在努力从商店的反应useEffect钩子中收集价值。

店铺:

object CounterModel { // Modified sample from kotlin StateFlow doc
    private val _counter = MutableStateFlow(0) // private mutable state flow
    val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow

    fun inc() { _counter.value++ }
}

零件:

val counter = functionalComponent<RProps> {
    val (counterState, setCounter) = useState(CounterModel.counter.value)

    useEffect(listOf()) {
        // This does not work
        GlobalScope.launch { CounterModel.counter.collect { setCounter(it) } }
    }
    
    div {
        h1 {
            +"Counter: $counterState"
        }
        button {
            attrs.onClickFunction = { CounterModel.inc() }
        }
    }
}

当我直接调用CounterModel.counter.collect { setCounter(it) }它时抱怨Suspend function 'collect' should be called only from a coroutine or another suspend function.

你将如何实现这个useEffect钩子?

一旦订阅生效,您将如何取消订阅(使用useEffectWithCleanup而不是useEffect)?

标签: kotlinreact-hookskotlin-coroutines

解决方案


终于找到了解决办法。我们可以使用onEach对每个新值执行操作,然后使用 '订阅' launchIn。这将返回一个可以取消以进行清理的作业:

object CounterStore {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> get() = _counter
    
    fun inc() { _counter.value++ }
}

val welcome = functionalComponent<RProps> {
    val (counter, setCounter) = useState(CounterStore.counter.value)

    useEffectWithCleanup(listOf()) {
        val job = CounterStore.counter.onEach { setCounter(it) }.launchIn(GlobalScope)
        return@useEffectWithCleanup { job.cancel() }
    }

    div {
        +"Counter: $counter"
    }
    button {
        attrs.onClickFunction = { CounterStore.inc() }
        +"Increment"
    }
}

我们可以将此 StateFlow 逻辑提取到自定义反应钩子中:

fun <T> useStateFlow(flow: StateFlow<T>): T {
    val (state, setState) = useState(flow.value)

    useEffectWithCleanup(listOf()) {
        val job = flow.onEach { setState(it) }.launchIn(GlobalScope)
        return@useEffectWithCleanup { job.cancel() }
    }
    
    return state
}

并在我们的组件中像这样使用它:

val counter = useStateFlow(CounterStore.counter)

完整的项目可以在这里找到。Flow-Api 是非常实验性的,所以这可能不是最终的解决方案 :)


推荐阅读