我正在尝试用kotlin 1.4-M2为kotlin-react和functionalComponent创建一个小的反例。该示例应使用kotlinx.coroutines.flow。我正在努力从reacts 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)?
发布于 2020-07-03 15:05:20
终于找到了解决方案。我们可以使用onEach为每个新值执行操作,然后使用launchIn 'subscribe‘。这将返回一个可以取消清理的作业:
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逻辑提取到一个自定义的react钩子中:
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)完整的项目可以在here上找到。Flow-Api是非常实验性的,所以这可能不是最终的解决方案:)
发布于 2020-09-20 22:39:26
如果在调用setState之前检查该值没有更改,这一点非常重要,否则渲染会发生两次
external interface ViewModelProps : RProps {
var viewModel : MyViewModel
}
val App = functionalComponent<ViewModelProps> { props ->
val model = props.viewModel
val (state, setState) = useState(model.stateFlow.value)
useEffectWithCleanup {
val job = model.stateFlow.onEach {
if (it != state) {
setState(it)
}
}.launchIn(GlobalScope)
return@useEffectWithCleanup { job.cancel() }
}
}https://stackoverflow.com/questions/62690722
复制相似问题