compose-state-hoisting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCompose state hoisting
Compose状态提升
Core principle
核心原则
Hoist state only as far as the logic needs it. Keep simple UI element state local, move shared UI element state to the lowest common composable owner, extract a plain state holder when UI-only behavior becomes a concept, and use a screen state holder when business logic or app data is involved.
仅在逻辑需要的范围内提升状态。将简单UI元素状态保留在本地,将共享UI元素状态移至最低层级的共同可组合项所有者,当纯UI行为形成独立概念时提取普通状态持有者类,涉及业务逻辑或应用数据时则使用屏幕级状态持有者。
Decision guide
决策指南
| Situation | Owner |
|---|---|
| One composable reads/writes simple state | Keep local with |
| Sibling or parent composables need to read/write it | Hoist state and events to their lowest common composable ancestor |
| Related UI element state plus UI logic is making a composable hard to read, preview, or test | Extract a plain state holder class remembered in composition |
| Repository calls, persistence, business rules, or screen UI state production are involved | Use a screen-level state holder such as a |
UI element state includes things like expansion, sheet visibility, scroll position, focus, text field editing state, selection, and animation/interaction state. Screen UI state is app data prepared for display.
If UI element state is an input to business logic, it may need to live in the screen state holder too. For example, text used to query repository-backed suggestions belongs with the state holder that produces those suggestions.
| 场景 | 所有者 |
|---|---|
| 单个可组合项读写简单状态 | 使用 |
| 兄弟或父级可组合项需要读写该状态 | 将状态和事件提升至它们的最低共同可组合项祖先 |
| 相关UI元素状态加上UI逻辑导致可组合项难以阅读、预览或测试 | 提取一个在组合中被remember的普通状态持有者类 |
| 涉及仓库调用、持久化、业务规则或屏幕UI状态生成 | 使用屏幕级状态持有者,例如 |
UI元素状态包括展开状态、底部栏可见性、滚动位置、焦点、文本框编辑状态、选择状态以及动画/交互状态。屏幕UI状态是为展示准备的应用数据。
如果UI元素状态是业务逻辑的输入,它可能也需要存放在屏幕级状态持有者中。例如,用于查询仓库返回建议的文本,应该与生成这些建议的状态持有者放在一起。
Plain state holder trigger
普通状态持有者提取触发条件
Extract a plain state holder when several of these are true:
- Multiple related values are coordinated by the same callbacks.
remember - Scroll, focus, text, selection, or sheet state needs named operations such as ,
clear,submit, orjumpToTop.openFilters - Derived UI flags are scattered through the composable.
- Child composables receive mechanics they do not conceptually own.
- Previews or tests must drive a long sequence of UI details to check one behavior.
- Helper functions need many state parameters just to keep the composable readable.
Do not extract for one boolean, one text field, or trivial show/hide logic. Ceremony is not separation of concerns.
当满足以下多个条件时,提取普通状态持有者:
- 多个相关的值由相同的回调协调。
remember - 滚动、焦点、文本、选择或底部栏状态需要命名操作,例如、
clear、submit或jumpToTop。openFilters - 派生UI标志分散在可组合项中。
- 子可组合项接收了它们在概念上并不拥有的机制。
- 预览或测试必须驱动一系列UI细节才能验证一个行为。
- 辅助函数需要多个状态参数才能保持可组合项的可读性。
不要为单个布尔值、单个文本框或简单的显示/隐藏逻辑提取状态持有者。繁琐的形式不等于关注点分离。
Pattern
模式
Use a plain class for UI element state and UI logic, plus a function for composition-owned objects:
remember...Statekotlin
@Stable
class ProductSearchState(
query: String,
private val listState: LazyListState,
private val focusRequester: FocusRequester,
) {
var query by mutableStateOf(query)
private set
var filtersOpen by mutableStateOf(false)
private set
val canClear: Boolean
get() = query.isNotEmpty()
fun updateQuery(value: String) {
query = value
}
fun clear() {
query = ""
focusRequester.requestFocus()
}
suspend fun jumpToTop() {
listState.animateScrollToItem(0)
}
}
@Composable
fun rememberProductSearchState(
initialQuery: String = "",
listState: LazyListState = rememberLazyListState(),
focusRequester: FocusRequester = remember { FocusRequester() },
): ProductSearchState {
return remember(listState, focusRequester) {
ProductSearchState(initialQuery, listState, focusRequester)
}
}The composable renders from the state holder and calls intent-style methods. If a parent needs to coordinate the same UI behavior, accept the state holder as a parameter with a default:
kotlin
@Composable
fun ProductSearchPanel(
state: ProductSearchState = rememberProductSearchState(),
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
SearchField(
query = state.query,
onQueryChange = state::updateQuery,
onClear = state::clear,
)
JumpToTopButton(onClick = {
scope.launch { state.jumpToTop() }
})
}使用普通类存储UI元素状态和UI逻辑,再配合函数处理组合所属对象:
remember...Statekotlin
@Stable
class ProductSearchState(
query: String,
private val listState: LazyListState,
private val focusRequester: FocusRequester,
) {
var query by mutableStateOf(query)
private set
var filtersOpen by mutableStateOf(false)
private set
val canClear: Boolean
get() = query.isNotEmpty()
fun updateQuery(value: String) {
query = value
}
fun clear() {
query = ""
focusRequester.requestFocus()
}
suspend fun jumpToTop() {
listState.animateScrollToItem(0)
}
}
@Composable
fun rememberProductSearchState(
initialQuery: String = "",
listState: LazyListState = rememberLazyListState(),
focusRequester: FocusRequester = remember { FocusRequester() },
): ProductSearchState {
return remember(listState, focusRequester) {
ProductSearchState(initialQuery, listState, focusRequester)
}
}可组合项从状态持有者获取数据进行渲染,并调用意图式方法。如果父级需要协调相同的UI行为,可以将状态持有者作为参数传入并设置默认值:
kotlin
@Composable
fun ProductSearchPanel(
state: ProductSearchState = rememberProductSearchState(),
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
SearchField(
query = state.query,
onQueryChange = state::updateQuery,
onClear = state::clear,
)
JumpToTopButton(onClick = {
scope.launch { state.jumpToTop() }
})
}Composition ownership
组合所有权
Plain state holders created with follow the composable lifecycle. This makes them a good home for Compose UI objects such as , , , , and .
rememberLazyListStateFocusRequesterPagerStateDrawerStateTextFieldStateKeep suspend UI operations that require a frame clock, such as scroll or drawer animations, in a composition-scoped coroutine (, , or another composition-owned scope). Do not move those calls to .
rememberCoroutineScopeLaunchedEffectviewModelScope使用创建的普通状态持有者遵循可组合项的生命周期。这使得它们非常适合存放Compose UI对象,例如、、、和。
rememberLazyListStateFocusRequesterPagerStateDrawerStateTextFieldState需要帧时钟的挂起UI操作(如滚动或抽屉动画)应放在组合作用域的协程中(、或其他组合所属作用域)。不要将这些调用移至。
rememberCoroutineScopeLaunchedEffectviewModelScopeSaving state
状态保存
Use or a custom only for values that should survive Activity or process recreation, such as a query string, selected filter IDs, or a current tab key.
rememberSaveableSaverDo not try to save runtime objects like , , coroutine scopes, or callbacks directly. Save the minimal serializable values needed to rebuild behavior.
LazyListStateFocusRequester仅对需要在Activity或进程重建后保留的值使用或自定义,例如查询字符串、选中的过滤器ID或当前标签键。
rememberSaveableSaver不要尝试直接保存运行时对象,如、、协程作用域或回调。只需保存重建行为所需的最小可序列化值即可。
LazyListStateFocusRequesterCommon mistakes
常见错误
| Mistake | Fix |
|---|---|
| Hoisting every local state value to a parent "just in case" | Hoist to the lowest owner that actually reads or writes it |
| Extracting a plain state holder for one boolean | Keep simple private UI state local |
| Putting repository calls or product rules in a Compose state holder | Move that logic to a screen state holder such as a |
| Keeping text or selection local when it drives repository-backed screen state | Move that input to the screen state holder with the business logic |
| Passing a state holder deep into unrelated children | Pass plain values and callbacks unless the child truly coordinates the holder's behavior |
| Treating the holder as a dumping ground for a whole screen | Split by cohesive UI behavior, such as search input, sheet coordination, or list controls |
Calling animation suspend functions from | Use a composition-scoped coroutine |
| 错误 | 修复方案 |
|---|---|
| 为了“以防万一”将所有本地状态提升至父级 | 仅将状态提升至实际需要读写它的最低层级所有者 |
| 为单个布尔值提取普通状态持有者 | 将简单的私有UI状态保留在本地 |
| 将仓库调用或产品规则放在Compose状态持有者中 | 将该逻辑移至屏幕级状态持有者,例如 |
| 当文本或选择状态驱动仓库返回的屏幕状态时,仍将其保留在本地 | 将该输入移至包含业务逻辑的屏幕级状态持有者中 |
| 将状态持有者深层传递给无关的子项 | 除非子项真正需要协调持有者的行为,否则传递普通值和回调即可 |
| 将持有者当作整个屏幕的“垃圾场” | 按内聚的UI行为拆分,例如搜索输入、底部栏协调或列表控制 |
从 | 使用组合作用域的协程 |
Related
相关内容
- — correct local
compose-state-authoringand mutable state authoring.remember - — split screen state-holder wiring from plain state-driven UI rendering.
compose-state-holder-ui-split - — choose effect APIs and composition-scoped coroutine boundaries.
compose-side-effects - — focus state, requesters, and keyboard/D-pad behavior.
compose-focus-navigation
- — 正确的本地
compose-state-authoring和可变状态编写方式。remember - — 将屏幕状态持有者的连接逻辑与纯状态驱动的UI渲染分离。
compose-state-holder-ui-split - — 选择副作用API和组合作用域的协程边界。
compose-side-effects - — 焦点状态、请求器以及键盘/方向键行为。
compose-focus-navigation