文章目录
CompositionLocal 是 Jetpack Compose 中一个非常重要的机制,它允许你在组合树中隐式地向下传递数据,而不需要通过每个 Composable 函数的参数手动传递
为什么需要 CompositionLocal
在 Compose 中,通常通过参数将数据传递给子函数。但在复杂的应用中,某些数据(如主题颜色、字体样式、当前登录用户信息或配置信息)几乎每个 UI 组件都需要访问。
如果通过参数层层传递:
代码冗余:中间层的函数即使不需要这些数据,也必须定义参数并向下转发。
难以维护:一旦需要增加一个全局参数,可能需要修改数十个函数的签名。
CompositionLocal 解决了这个问题:你在高层提供一个值,底层的任何 Composable 都可以直接访问它。
核心概念与创建方式
创建 CompositionLocal 主要有两种方式:
-
A.
compositionLocalOf特性:当提供的值发生改变时,Compose 只会让读取该值的 Composable 发生重组。
-
B.
staticCompositionLocalOf特性:当值改变时,Compose 会重新读取并重组整个内容树,而不是只重组读取它的部分。
优点:存储和读取的性能比 compositionLocalOf 略好(因为它不需要跟踪依赖关系)。
重要属性 current:
kotlin
class CompositionLocal {
public inline val current: T
@ReadOnlyComposable @Composable get() = currentComposer.consume(this)
}
示例
kotlin
data class UserInfo(val name: String, val isAdmin: Boolean)
val LocalUserInfo = compositionLocalOf { UserInfo("default", false) }
// val LocalUserInfo = staticCompositionLocalOf { UserInfo("default", false) }
kotlin
@Composable
fun CompLocalTest() {
// state 记忆
var currentUser by remember { mutableStateOf(UserInfo("Manager", true)) }
// 通过 CompositionLocalProvider 向下传递,本质是定义了共享它的作用域
CompositionLocalProvider(LocalUserInfo provides currentUser) {
// 这里的子组件及其所有后代都可以访问到 currentUser
Dashboard1()
Dashboard2() // 内部使用 local.current
}
val user = LocalUserInfo.current
Text(text = "hi, ${user.name}! Admin: ${user.isAdmin}",
modifier = Modifier.padding(top = 40.dp))
Button(onClick = {
currentUser = UserInfo("xx", false)
}, modifier = Modifier.padding(top = 100.dp)) {
Text("change user:${currentUser}")
}
}
@Composable
private fun Dashboard1() {
SideEffect {
Log.d("Dashboard1", "Dashboard1: 进入组合")
}
}
@Composable
private fun Dashboard2() {
SideEffect {
Log.d("Dashboard2", "Dashboard2: 进入组合")
}
UserInfoLabel()
}
@Composable
private fun UserInfoLabel() {
SideEffect {
Log.d("UserInfoLabel", "UserInfoLabel: 进入组合")
}
// 获取当前作用域内的值
val user = LocalUserInfo.current
Text(text = "Welcome, ${user.name}! Admin: ${user.isAdmin}")
}
- 通过
compositionLocalOf或staticCompositionLocalOf创建的类型,它的直接或间接父类 是ProvidableCompositionLocal,这个父类中提供了一个 中缀函数:
kotlin
public infix fun provides(value: T): ProvidedValue<T> = defaultProvidedValue(value)
CompositionLocalProvider 函数接收一个 ProvidedValue<*> 类型的参数
kotlin
public fun CompositionLocalProvider(value: ProvidedValue<*>, content: @Composable () -> Unit) {
currentComposer.startProvider(value)
content()
currentComposer.endProvider()
}
这个参数类型就是上面的中缀函数的返回类型
SideEffect每次进入组合阶段时都会调用。重组当然也会。- CompLocalTest() 功能:
currentUser 声明定义成 记忆状态,使用了
remember { mutableStateOf(...) }
CompositionLocalProvider向内部 content 传递了 currentUser界面中 第一个组件是
text = "Welcome ...用户name是 "xx"第二个 padding(top = 40.dp) 的Text显示的是 默认用户name是 "default"
第三个组件按钮 padding(top = 100.dp),显示的是 用户name是 "Manager"
Button 点击修改了 记忆状态对象 currentUser,引起了读取 currentUser 的可组合项发生重组。Button中的Text("change user:${currentUser}")、UserInfoLabel 中的text = "Welcome ...肯定会发生重组若使用
... = compositionLocalOf {},点击后只有这两处会发生重组。此时日志输出:UserInfoLabel: 进入组合
若使用... = staticCompositionLocalOf {},点击后不光这两处会发生重组;CompositionLocalProvider的 content 内的 所有可组合项包括子项都会重组。此时日志输出:Dashboard1: 进入组合
Dashboard2: 进入组合
UserInfoLabel: 进入组合
适用场景
compositionLocalOf:需要局部灵活变动、且变动频繁的数据。
staticCompositionLocalOf:需要局部灵活变动、但几乎一成不变的数据(如皮肤、样式)