这两个函数都是用来创建 CompositionLocal
的,但它们在重组行为 和性能特性上有重要区别。
1. 基本区别对比
特性 | compositionLocalOf |
staticCompositionLocalOf |
---|---|---|
重组范围 | 只有读取该值的具体组件重组 | 整个提供范围内的所有组件都会重组 |
性能开销 | 较高(需要跟踪依赖) | 较低(不需要跟踪依赖) |
适用场景 | 值可能频繁变化 | 值很少变化 或永不变化 |
内存使用 | 需要维护依赖关系 | 不需要维护依赖关系 |
2. compositionLocalOf
- 精细重组
工作原理
当提供的值改变时,只有直接读取该值的组件会重组,其他不相关的组件不会受影响。
代码示例
kotlin
// 创建 - 适合可能变化的值
val LocalUserScore = compositionLocalOf { 0 }
@Composable
fun GameScreen() {
var score by remember { mutableStateOf(0) }
// 提供可能变化的值
CompositionLocalProvider(LocalUserScore provides score) {
Column {
ScoreDisplay() // 只在这个组件中读取 score
GameBoard() // 不读取 score,不会因 score 变化而重组
Controls() // 不读取 score,不会因 score 变化而重组
}
}
Button(onClick = { score++ }) {
Text("增加分数")
}
}
@Composable
fun ScoreDisplay() {
val score = LocalUserScore.current // 只有这个组件会因 score 变化而重组
Text("分数: $score", color = Color.Red)
}
@Composable
fun GameBoard() {
// 这个组件不读取 score,所以 score 变化时不会重组
Text("游戏区域", color = Color.Blue)
}
重组行为
ini
初始状态:
score = 0 → ScoreDisplay 显示 "分数: 0"
点击按钮后:
score = 1 → 只有 ScoreDisplay 重组,显示 "分数: 1"
GameBoard 和 Controls 不会重组
3. staticCompositionLocalOf
- 批量重组
工作原理
当提供的值改变时,整个 CompositionLocalProvider
范围内的所有组件都会重组,无论它们是否读取了这个值。
代码示例
kotlin
// 创建 - 适合很少变化的值
val LocalAppTheme = staticCompositionLocalOf { LightTheme }
@Composable
fun App() {
var currentTheme by remember { mutableStateOf(LightTheme) }
// 提供很少变化的值
CompositionLocalProvider(LocalAppTheme provides currentTheme) {
Column {
Header() // 即使不读取 theme,theme 变化时也会重组
Content() // 即使不读取 theme,theme 变化时也会重组
Footer() // 即使不读取 theme,theme 变化时也会重组
}
}
Button(onClick = {
currentTheme = if (currentTheme == LightTheme) DarkTheme else LightTheme
}) {
Text("切换主题")
}
}
@Composable
fun Header() {
val theme = LocalAppTheme.current // 读取 theme
Text("应用标题", color = theme.primaryColor)
}
@Composable
fun Content() {
// 这个组件不读取 theme,但 theme 变化时也会重组!
Text("主要内容")
}
@Composable
fun Footer() {
// 这个组件不读取 theme,但 theme 变化时也会重组!
Text("页脚信息")
}
重组行为
ini
初始状态:
theme = LightTheme → 所有组件正常渲染
切换主题后:
theme = DarkTheme → Header、Content、Footer 全部都会重组!
4. 性能影响深度分析
compositionLocalOf
的性能开销
kotlin
val LocalCounter = compositionLocalOf { 0 }
@Composable
fun PerformanceExample() {
var counter by remember { mutableStateOf(0) }
CompositionLocalProvider(LocalCounter provides counter) {
// Compose 需要跟踪哪些组件读取了 LocalCounter
// 这需要额外的内存和计算开销
ExpensiveComponent1() // 读取 counter
ExpensiveComponent2() // 不读取 counter
ExpensiveComponent3() // 读取 counter
}
}
开销来源:
- 需要维护依赖关系图
- 需要跟踪每个读取点
- 重组时需要检查具体依赖
staticCompositionLocalOf
的性能优势
kotlin
val LocalAppConfig = staticCompositionLocalOf { AppConfig() }
@Composable
fun StaticPerformanceExample() {
val config = remember { AppConfig() } // 记住,不会变化
CompositionLocalProvider(LocalAppConfig provides config) {
// 不需要跟踪依赖关系
// 如果 config 变化(但实际不会),所有组件都会重组
ExpensiveComponent1()
ExpensiveComponent2()
ExpensiveComponent3()
}
}
性能优势:
- 不需要维护依赖关系
- 内存占用更小
- 初始设置更快
5. 实际应用场景选择
使用 compositionLocalOf
的场景 ✅
值可能频繁变化的情况:
kotlin
// 1. 用户界面状态
val LocalUiState = compositionLocalOf { UiState.Loading }
// 2. 加载状态
val LocalLoadingState = compositionLocalOf { false }
// 3. 实时数据
val LocalLiveData = compositionLocalOf { 0 }
// 4. 动画值
val LocalAnimationProgress = compositionLocalOf { 0f }
使用 staticCompositionLocalOf
的场景 ✅
值很少变化或永不变化的情况:
kotlin
// 1. 导航控制器(应用生命周期内通常不变)
val LocalNavController = staticCompositionLocalOf<NavHostController> {
error("No NavController provided")
}
// 2. 应用配置(启动后不变)
val LocalAppConfig = staticCompositionLocalOf { AppConfig() }
// 3. 依赖注入容器(通常不变)
val LocalDependencyContainer = staticCompositionLocalOf<DIContainer> {
error("No DI Container provided")
}
// 4. 主题配置(切换频率低)
val LocalAppTheme = staticCompositionLocalOf { LightTheme }
6. 错误使用示例 ❌
错误使用 staticCompositionLocalOf
kotlin
// ❌ 错误:用于频繁变化的值
val LocalAnimationValue = staticCompositionLocalOf { 0f }
@Composable
fun AnimatedComponent() {
val animatedValue by animateFloatAsState(targetValue = 1f)
// 错误!animatedValue 每帧都在变化
CompositionLocalProvider(LocalAnimationValue provides animatedValue) {
// 导致整个范围内的组件每帧都重组!
ChildComponent1()
ChildComponent2()
ChildComponent3()
}
}
错误使用 compositionLocalOf
kotlin
// ❌ 错误:用于永不变化的值
val LocalAppVersion = compositionLocalOf { "1.0.0" }
@Composable
fun App() {
val version = "1.0.0" // 永远不会变化
// 错误!增加了不必要的性能开销
CompositionLocalProvider(LocalAppVersion provides version) {
// Compose 会维护依赖跟踪,但实际不需要
MainScreen()
}
}
7. 最佳实践总结
选择指南
条件 | 推荐选择 |
---|---|
值永不变化 | staticCompositionLocalOf |
值很少变化(如主题、配置) | staticCompositionLocalOf |
值可能频繁变化 | compositionLocalOf |
不确定值的变化频率 | 从 compositionLocalOf 开始,性能有问题时再优化 |
性能优化技巧
kotlin
// 好的实践:对不变的值使用 static
val LocalDatabase = staticCompositionLocalOf<Database> {
error("No database provided")
}
// 好的实践:对可能变化的值使用常规版本
val LocalUserPreferences = compositionLocalOf<UserPreferences> {
UserPreferences.getDefault()
}
// 使用 remember 避免不必要的重组
@Composable
fun OptimizedComponent() {
val stableConfig = remember { AppConfig() } // 记住不变的值
CompositionLocalProvider(
LocalDatabase provides database, // static,不变
LocalUserPreferences provides preferences // 常规,可能变化
) {
AppContent()
}
}
总结
简单记忆法则:
- 值会变 → 用
compositionLocalOf
(精细重组) - 值不变 → 用
staticCompositionLocalOf
(性能更好)
核心区别:
compositionLocalOf
:谁用谁重组(精确但开销大)staticCompositionLocalOf
:一变全都重组(粗放但高效)
正确选择可以显著影响应用的性能,特别是在复杂的组件树中!