Skip to content

隐式传参 (CompositionLocal)

源:使用 CompositionLocal 将数据作用域限定在局部

通常,数据通过参数向下流经 UI 树。但这对于广泛使用的数据(如颜色、字体、Context)来说很麻烦。CompositionLocal 允许您隐式地向下传递数据,而无需通过每个函数的参数。

1. 什么是 CompositionLocal?

它是 Compose 树中的一个“局部变量”。你肯定用过它们:

  • LocalContext.current
  • LocalConfiguration.current
  • MaterialTheme.colorScheme (底层也是 CompositionLocal)

2. 自定义 CompositionLocal

步骤 A: 创建 Key

你需要决定如果没人提供值,默认值是什么。

kotlin
// 1. staticCompositionLocalOf: 值变化时,重组整个读取它的作用域(适用于极少变化的值,如 Repository)
val LocalAnalytics = staticCompositionLocalOf<Analytics> {
    error("No Analytics provided")
}

// 2. compositionLocalOf: 值变化时,只重组读取它的地方(适用于频繁变化的值,如主题颜色)
val LocalAppColors = compositionLocalOf { Color.White }

步骤 B: 提供值 (Provider)

使用 CompositionLocalProvider 将值绑定到特定的子树。

kotlin
@Composable
fun App() {
    val analytics = remember { AnalyticsImpl() }
    
    CompositionLocalProvider(LocalAnalytics provides analytics) {
        // 在这个作用域内,都可以通过 LocalAnalytics.current 获取到 analytics
        // 而不需要通过参数一层层传下去
        HomeScreen()
    }
}

步骤 C: 读取值

kotlin
@Composable
fun HomeScreen() {
    // 隐式获取,像变魔术一样
    val analytics = LocalAnalytics.current
    
    Button(onClick = { analytics.logEvent("click") }) {
        Text("Click Me")
    }
}

3. 最佳实践

谨慎使用

不要滥用 CompositionLocal。它使依赖关系变得隐式,导致代码难以测试和理解。

适合放入 CompositionLocal 的数据:

  1. 环境数据:主题、字体、语言环境。
  2. 系统服务:Context, Resources, WindowInfo。
  3. 全局单例:Analytics, Logger, Dependency Injection Container (如 Hilt 的 EntryPoint)。

不适合的数据:

  1. ViewModel:应该通过参数显式传递。
  2. 业务数据:如 User 对象、List 数据。