在 Jetpack Compose 中,derivedStateOf
用来将一个或多个State转成另一个State。derivedStateOf{...}
的block中可以依赖其他State创建并返回一个DerivedState,当block中依赖的State发生变化时,会更新此DerivedState,依赖此DerivedState的所有Composable会因其变化而重组。
代码对比
写法一(无依赖项)
kotlin
val list by remember { mutableStateOf(listOf("Michel", "Mike", "Jerry Black")) }
val targetCount by remember { mutableIntStateOf(10) }
val targetList by remember {
derivedStateOf { list.filter { it.length >= targetCount } }
}
写法二(显式依赖项)
kotlin
val list by remember { mutableStateOf(listOf("Michel", "Mike", "Jerry Black")) }
val targetCount by remember { mutableIntStateOf(10) }
val targetList by remember(list, targetCount) {
derivedStateOf { list.filter { it.length >= targetCount } }
}
关键区别分析
特性 | 写法一 (无依赖项) | 写法二 (显式依赖项) |
---|---|---|
依赖跟踪 | 自动跟踪计算中使用的所有状态 | 依赖项必须手动指定 |
重新创建时机 | 只在初始组合时创建一次 | 当 list 或 targetCount 变化时重新创建 |
内存效率 | 更高 - 对象只创建一次 | 较低 - 依赖变化时重新创建对象 |
计算触发 | 依赖变化时自动重新计算 | 依赖变化时自动重新计算 |
推荐场景 | 大多数场景 | 当派生状态依赖外部变量时 |
潜在风险 | 如果计算中使用了非状态变量可能出错 | 可能遗漏依赖项导致状态过期 |
详细说明
1. 依赖跟踪机制
-
写法一 :
derivedStateOf
会自动跟踪在其计算函数中访问的任何状态(list
和targetCount
)。当这些状态变化时,它会自动重新计算。 -
写法二 :依赖项必须显式声明在
remember
的键中。如果遗漏了某个依赖项,当该依赖变化时不会触发重新计算。
2. 对象生命周期
kotlin
// 写法一 - 只创建一次
val derivedState = remember {
derivedStateOf { /* ... */ } // 只执行一次
}
// 写法二 - 依赖变化时重新创建
val derivedState = remember(key1, key2) {
derivedStateOf { /* ... */ } // 当 key1 或 key2 变化时重新执行
}
3. 性能影响
- 写法一 更高效,因为
derivedStateOf
实例只创建一次 - 写法二 在依赖变化时会有额外开销:
- 创建新的
derivedStateOf
实例 - 丢弃旧实例
- 注册新的状态监听
- 创建新的
4. 正确性考虑
两种写法在计算逻辑上结果相同,但写法二需要确保:
- 包含所有必要的依赖项
- 依赖项是稳定类型(避免不必要的重新创建)
何时使用哪种写法
✅ 优先使用写法一的情况:
- 当派生状态只依赖于可组合函数内的状态时
- 当依赖项是稳定的状态对象时
- 大多数常见场景
⚠️ 考虑写法二的情况:
-
依赖外部参数时:
kotlin@Composable fun FilteredList(externalParam: Int) { val list = // ... val filteredList by remember(list, externalParam) { derivedStateOf { list.filter { it.length >= externalParam } } } }
-
依赖不稳定对象时:
kotlindata class FilterCriteria(val minLength: Int) // 非稳定类型 val filteredList by remember(criteria) { derivedStateOf { list.filter { it.length >= criteria.minLength } } }
最佳实践建议
-
默认使用写法一:
kotlin// 推荐 - 简洁高效 val result by remember { derivedStateOf { computeValue(stateA, stateB) } }
-
当依赖外部变量时使用写法二:
kotlinval result by remember(externalVar) { derivedStateOf { compute(state, externalVar) } }
-
避免不必要的重新创建:
kotlin// 不推荐 - 依赖项变化会导致不必要的重新创建 val result by remember(list, count) { derivedStateOf { /* 简单计算 */ } }
-
复杂派生状态使用自定义记忆:
kotlinval complexResult by remember { derivedStateOf { // 计算密集型操作 heavyComputation(list, count) } }
可视化重组行为
假设 list
变化但 targetCount
不变:
- 写法一 :只重新计算
derivedStateOf
,不重新创建对象 - 写法二 :重新创建
derivedStateOf
对象并重新计算
结论
derivedStateOf 只能监听block内的State,一个非State类型数据的变化则可以通过remember的key进行监听
大多数情况下,写法一(无显式依赖项)是更好的选择:
- 更简洁
- 更高效(避免不必要的对象创建)
- 自动跟踪所有依赖
- 符合 Compose 的设计哲学
仅当派生状态依赖外部参数或不稳定对象时,才需要使用写法二(显式依赖项)。在代码实践中,优先选择写法一可以创建更高效、更易维护的 Compose 应用。