什么是 @ReadOnlyComposable
在 Compose 中,普通 @Composable 函数不仅负责绘制 UI,还会参与 Composition 的状态管理。每次调用通常都会在内部 Slot Table 中创建一个新的 Group(组合节点),用于:
- 跟踪函数调用位置
- 管理状态
- 支持 Recomposition
- 实现 Skip(跳过未变化部分)
而 @ReadOnlyComposable 本质上是一种特殊优化,它向 Compose 编译器做出承诺:
"我只是读取上下文数据,不生成 UI,不创建新的组合节点,也不会产生副作用。"
常见场景:
kotlin
@Composable
@ReadOnlyComposable
fun currentDensity(): Density {
return LocalDensity.current
}
它只是读取 CompositionLocal 的值。
普通 @Composable 会发生什么
例如:
kotlin
@Composable
fun UserThemeColor(): Color {
return MaterialTheme.colorScheme.primary
}
编译器在背后大致会变成:
kotlin
fun UserThemeColor(
composer: Composer,
changed: Int
): Color {
composer.startReplaceableGroup(...)
val color = MaterialTheme.colorScheme.primary
composer.endReplaceableGroup()
return color
}
这里:
startReplaceableGroup()endReplaceableGroup()
会创建 Composition Group。这样 Compose 才能记录:
- 当前函数位置
- 状态依赖
- 是否可以跳过重组
@ReadOnlyComposable 的变化
加上:
kotlin
@Composable
@ReadOnlyComposable
fun UserThemeColor(): Color {
return MaterialTheme.colorScheme.primary
}
编译器会省略 Group 创建:
kotlin
fun UserThemeColor(
composer: Composer,
changed: Int
): Color {
return MaterialTheme.colorScheme.primary
}
差异
普通 Composable:
kotlin
startGroup()
读取状态
endGroup()
ReadOnlyComposable:
kotlin
读取状态
少了:
- Group 分配
- Slot Table 写入
- 重组跟踪成本
这就是它的性能收益来源
为什么 Compose 官方会使用它
在 Material3 源码里能看到类似:
kotlikn
val ColorScheme.primary: Color
@Composable
@ReadOnlyComposable
get() = LocalColorScheme.current.primary
原因:主题颜色可能被频繁访问:
kotlin
Text(
color = MaterialTheme.colorScheme.primary
)
Button(
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
)
如果每次都创建新的 Group:
- Slot Table 会变大
- 重组跟踪更多节点
- 性能增加额外开销
@ReadOnlyComposable 避免了这些成本。
什么时候不能使用
下面这种是错误示例:
kotlin
@Composable
@ReadOnlyComposable
fun Wrong() {
Text("Hello")
}
原因:会创建 UI 节点。
scss
Text()
再比如:
kotlin
@Composable
@ReadOnlyComposable
fun Wrong() {
val state = remember {
mutableStateOf(0)
}
}
原因:
remember:
- 写入 Slot Table
- 创建状态节点
这违反了"只读"的承诺。
还有:
kotlin
LaunchedEffect()
SideEffect()
DisposableEffect()
都不能放进去。
可以把它理解成什么
可以把:@Composable理解为:"我可能创建 UI,也可能管理状态。" 而@ReadOnlyComposable则是: "我只是读取 Compose 环境变量。" 它更像一个"零成本访问器(accessor)"。
实际开发建议
大部分业务代码几乎不需要自己写 @ReadOnlyComposable。
适合的场景非常少:
- 读取
CompositionLocal - 读取主题数据
- 读取 Density
- 读取 LayoutDirection
- 读取 Context
例如:
less
@Composable
@ReadOnlyComposable
fun screenWidth(): Dp {
return LocalConfiguration.current.screenWidthDp.dp
}
不适合:
- UI 组件
- remember
- Effect API
- 状态修改
- 动画逻辑
Compose 官方也主要把它用于框架内部的微优化,而不是日常业务开发