原文地址:# 在Compose中像使用redux一样轻松管理全局状态
写在前面
本文中提及的use
开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关系复杂的状态管理,专心于业务与UI组件。
这是系列文章的第五篇,前文:
- 在Compose中使用useRequest轻松管理网络请求
- 在Compose中使用状态提升?我提升个P...Provider
- 在Compose中父组件如何调用子组件的函数?
- 在Compose中方便的使用MVI思想?试试useReducer!
也许只有 useContext
、useReducer
是不够的
书接上回,上次的文章发布后有小伙伴在评论区留言,谈到了关于在组件之间保存状态的问题,我也给予了回答,那就是使用 useContext 进行进一步的状态提升。
但是在之前版本的ComposeHooks 并没有很方便的方式让我们讲 useContext
与 useReducer
配合起来,只使用这二者在暴露多个状态时非常麻烦。
现在全新版本的 redux-react 风格的 hook来啦,它就是 useSelector
、useDispatch
,现在你可以轻松的构建全局状态,并通过这两个钩子轻松的跨组件获取状态与dispatch函数。它是基于 useContext
的进一步封装,现在你可以不需要再自己去配置 useContext
来管理状态暴露了!
如何使用
关于 reducer 的概念,请先查阅 在Compose中方便的使用MVI思想?试试useReducer!,一些概念我们就不再赘述了,这里依旧使用我最喜欢的 Todos 作为案例 (在Compose中使用状态提升?我提升个P...Provider)。
使用 createStore 创建并暴露全局状态
我们可以使用 createStore
函数来创建一个全局状态存储实例,例如:
// 状态对应的数据类
data class Todo(val name: String, val id: String)
// action
sealed interface TodoAction
data class AddTodo(val todo: Todo) : TodoAction
data class DelTodo(val id: String) : TodoAction
//reducer函数
val todoReducer: Reducer<List<Todo>, TodoAction> = { prevState: List<Todo>, action: TodoAction ->
when (action) {
is AddTodo -> buildList {
addAll(prevState)
add(action.todo)
}
is DelTodo -> prevState.filter { it.id != action.id }
}
}
// 创建 store
val store = createStore {
todoReducer with emptyList() // 使用中缀函数 with 来连接reducer函数与初始状态
}
在createStore 的闭包内,使用中缀函数 with 来连接 reducer函数 与 初始状态。
除此之外,我们还可以使用 named(alias){}
这个作用域函数,来创建一个带别名的状态存储,例如:
val store = createStore {
named("todo") { todoReducer with emptyList() }
}
然后我们在根组件使用 ReduxProvider
组件暴露全局状态存储。
@Composable
fun UseReduxExample() {
ReduxProvider(store = store) {
// 在这个闭包下的所有组件都能访问到 store 中的全局状态
}
}
前面的代码大抵差别不大,可以看作是使用 useReducer
的 MVI 改造。
关键点在于:val store = createStore { }
,这里我们需要通过 createStore
函数创建一个全局的状态存储对象,在函数闭包内,我们通过中缀函数 with
,连接一个 reducer函数 与一个 初始状态 .
然后我们使用 ReduxProvider(store = store)
将全局状态存储对象进行暴露。
useSelector 获取状态
现在我们的状态已经向下暴露了,因为我们已经将他提升到了 ReduxProvider
在其下的所有组件都可以使用useSelector
轻松的获取状态:
@Composable
fun TodoList() {
val todos = useSelector<List<Todo>>() //需要传递 状态 的类型
Column {
todos.map {
TodoItem(item = it)
}
}
}
useDispatch 获取 dispatch 函数
只有状态当然是不够的,在 MVI 中我们还需要使用 dispatch函数。
在最新版本中,你可以轻松的通过 useDispatch
来进行获取:
@Composable
fun Header() {
val dispatch = useDispatch<TodoAction>() //需要传递 Action 的类型
val (input, setInput) = useState("")
Row {
OutlinedTextField(
value = input,
onValueChange = setInput,
)
TButton(text = "add") {
dispatch(AddTodo(Todo(input, NanoId.generate())))
setInput("")
}
}
}
@Composable
fun TodoItem(item: Todo) {
val dispatch = useDispatch<TodoAction>()
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = item.name)
TButton(text = "del") {
dispatch(DelTodo(item.id))
}
}
}
多个状态、多个Store
请注意,val store = createStore { }
的闭包中可以传递多个状态,你只需要在这里通过 with
连接 reducer函数 与初始状态,就可以注册暴露多个状态到全局了:
例如:
val store = createStore {
otherReducer with OtherData("default", 18) //另一个 reducer
todoReducer with emptyList()
}
你也可以根据业务拆分 Store,在项目中创建多个 Store,例如:
// simple.kt
val simpleStore = createStore(arrayOf(logMiddleware())) {
simpleReducer with SimpleData("default", 18)
todoReducer with persistentListOf()
}
// fetch.kt
val fetchStore = createStore {
NetworkFetchAliases.forEach {
named(it) {
fetchReducer with NetFetchResult.Idle
}
}
}
在根组件中通过 +
扩展函数,来将所有 store 实例组合起来:
@Composable
fun App() {
ComposeHooksTheme {
// provide store for all components
ReduxProvider(store = simpleStore + fetchStore) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
useRoutes(routes = routes + subRequestRoutes + otherSubRoutes)
}
}
}
}
探索更多
项目开源地址:junerver/ComposeHooks
MavenCentral:hooks
implementation("xyz.junerver.compose:hooks:1.0.8")
欢迎使用、勘误、pr、star。