compose-state-deferred-reads
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCompose state deferred reads
Compose 状态延迟读取
Core principle
核心原则
State reads invalidate the phase that reads them. If a is read in a composable body, changes invalidate composition. If it is read in layout or draw, changes can invalidate only layout or draw. Frame-rate state such as scroll offsets, animations, and drag positions usually belongs in layout/draw, not composition.
State<T>The fix is structural: keep the or a provider lambda, and read the value inside a layout/draw callback.
State<T>状态读取会使读取该状态的阶段失效。如果在可组合项主体中读取,状态变化会使组合阶段失效。如果在布局或绘制阶段读取,状态变化只会使布局或绘制阶段失效。诸如滚动偏移、动画和拖拽位置这类帧率相关的状态通常属于布局/绘制阶段,而非组合阶段。
State<T>修复方法是结构化调整:保留或提供者lambda,在布局/绘制回调内部读取值。
State<T>When to use this skill
何时使用本技巧
- is passed to
val x by animate*AsState(...),Modifier.offset(x = ...),Modifier.size(...), or another value-form modifier.Modifier.graphicsLayer(...) - ,
LazyListState.firstVisibleItemScrollOffset,ScrollState.value, or gesture state is read in a composable body.Animatable.value - A composable takes ,
scrollOffset: Int,progress: Float, or similar frame-rate values.dragOffset: Offset - Recomposition counters climb during scroll, animation, or gestures even when data is stable.
- 被传递给
val x by animate*AsState(...)、Modifier.offset(x = ...)、Modifier.size(...)或其他值形式的修饰符。Modifier.graphicsLayer(...) - 在可组合项主体中读取、
LazyListState.firstVisibleItemScrollOffset、ScrollState.value或手势状态。Animatable.value - 可组合项接收、
scrollOffset: Int、progress: Float或类似的帧率相关值。dragOffset: Offset - 滚动、动画或手势过程中,即使数据稳定,重组计数器仍持续攀升。
1. Prefer block-form modifiers
1. 优先使用块形式修饰符
Several modifiers have value forms and block forms. The value form receives values already read in composition; the block form can read during layout or draw.
kotlin
// Before: animated value read in composition by the `by` delegate
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX by animateDpAsState(120.dp * selectedIndex)
Box(Modifier.offset(x = offsetX))
}
// After: State is kept, value is read in the layout-phase offset block
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX = animateDpAsState(120.dp * selectedIndex)
Box(
Modifier.offset {
IntOffset(offsetX.value.roundToPx(), 0)
},
)
}Common replacements:
| Composition read | Deferred read |
|---|---|
| |
| |
| |
The block is already draw-phase; the important part is that the read also happens inside that block.
drawBehindState.value部分修饰符同时提供值形式和块形式。值形式接收已在组合阶段读取的值;块形式可在布局或绘制阶段读取值。
kotlin
// Before: animated value read in composition by the `by` delegate
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX by animateDpAsState(120.dp * selectedIndex)
Box(Modifier.offset(x = offsetX))
}
// After: State is kept, value is read in the layout-phase offset block
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX = animateDpAsState(120.dp * selectedIndex)
Box(
Modifier.offset {
IntOffset(offsetX.value.roundToPx(), 0)
},
)
}常见替换方式:
| 组合阶段读取 | 延迟读取 |
|---|---|
| |
| |
| |
drawBehindState.value2. Pass providers across composable boundaries
2. 跨可组合项边界传递提供者
If the fast-changing value would cross a composable boundary, pass a provider lambda instead of a snapshot value:
kotlin
// Before: HomeScreen reads scroll offset in composition and passes the value down
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item { HeroImage(scrollOffset = listState.firstVisibleItemScrollOffset) }
}
}
@Composable
fun HeroImage(scrollOffset: Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer(translationY = -scrollOffset / 2f),
)
}
// After: the only read happens inside graphicsLayer
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item {
HeroImage(
scrollOffsetProvider = {
if (listState.firstVisibleItemIndex == 0) {
listState.firstVisibleItemScrollOffset
} else {
0
}
},
)
}
}
}
@Composable
fun HeroImage(scrollOffsetProvider: () -> Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer {
translationY = -scrollOffsetProvider() / 2f
},
)
}Suffix provider parameters with when that clarifies the deferred-read contract.
Provider如果快速变化的值需要跨可组合项边界传递,应传递提供者lambda而非快照值:
kotlin
// Before: HomeScreen reads scroll offset in composition and passes the value down
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item { HeroImage(scrollOffset = listState.firstVisibleItemScrollOffset) }
}
}
@Composable
fun HeroImage(scrollOffset: Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer(translationY = -scrollOffset / 2f),
)
}
// After: the only read happens inside graphicsLayer
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item {
HeroImage(
scrollOffsetProvider = {
if (listState.firstVisibleItemIndex == 0) {
listState.firstVisibleItemScrollOffset
} else {
0
}
},
)
}
}
}
@Composable
fun HeroImage(scrollOffsetProvider: () -> Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer {
translationY = -scrollOffsetProvider() / 2f
},
)
}当需要明确延迟读取约定时,可为提供者参数添加后缀。
Provider3. Other layout/draw read sites
3. 其他布局/绘制读取场景
State reads can also be deferred inside:
Modifier.layout { measurable, constraints -> ... }- Custom
Alignment.align(...) - ,
drawWithContent, and other draw modifiersdrawBehind - Block-form layer/layout modifiers such as and
graphicsLayer { ... }offset { ... }
Use these when the state changes where something is placed or painted. If the state decides which composables exist, it belongs in composition.
状态读取也可在以下场景中延迟:
Modifier.layout { measurable, constraints -> ... }- 自定义
Alignment.align(...) - 、
drawWithContent及其他绘制修饰符drawBehind - 块形式的图层/布局修饰符,如和
graphicsLayer { ... }offset { ... }
当状态会改变元素的位置或绘制方式时,使用上述方式。如果状态决定哪些可组合项存在,则应在组合阶段读取。
Quick reference
快速参考
| Symptom | Diagnosis | Fix |
|---|---|---|
| | Keep |
| Property-argument form uses composition values | Use |
| Fast-changing value crosses boundary | |
| Draw block still recomposes every frame | Value was read before draw block | Move the |
| State chooses between different UI branches | Composition decision | Keep the read in composition |
| 症状 | 诊断 | 修复方案 |
|---|---|---|
| | 保留 |
| 属性参数形式使用组合阶段的值 | 使用 |
| 快速变化的值跨边界传递 | |
| 绘制块仍每帧重组 | 值在绘制块之前被读取 | 将 |
| 状态决定不同UI分支的显示 | 组合阶段决策 | 保留组合阶段的读取操作 |
When NOT to apply
何时不适用
- The state controls which composables are emitted.
- The animation is one-shot, cheap, and clarity wins.
- You are writing tests where direct value assertions are simpler.
- Runtime evidence shows recomposition is not the bottleneck.
- 状态控制哪些可组合项被渲染。
- 动画是一次性的、开销低,且代码清晰度优先。
- 编写测试时,直接断言值更简单。
- 运行时证据表明重组并非性能瓶颈。
Related
相关内容
- - where state-holder vs plain UI split applies when passing providers/lambdas across boundaries.
compose-state-holder-ui-split - - parameter stability and compiler reports.
compose-stability-diagnostics - - child composables need a normal
compose-modifier-and-layout-styleparameter before callers can move visual reads into modifiers.modifier
- - 跨边界传递提供者/lambda时,状态持有者与纯UI拆分的适用场景。
compose-state-holder-ui-split - - 参数稳定性与编译器报告。
compose-stability-diagnostics - - 子可组合项需要常规
compose-modifier-and-layout-style参数,以便调用者将视觉读取操作移至修饰符中。modifier