Compose 两种 `derivedStateOf` 写法比较

在 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 } }
}

关键区别分析

特性 写法一 (无依赖项) 写法二 (显式依赖项)
依赖跟踪 自动跟踪计算中使用的所有状态 依赖项必须手动指定
重新创建时机 只在初始组合时创建一次 listtargetCount 变化时重新创建
内存效率 更高 - 对象只创建一次 较低 - 依赖变化时重新创建对象
计算触发 依赖变化时自动重新计算 依赖变化时自动重新计算
推荐场景 大多数场景 当派生状态依赖外部变量时
潜在风险 如果计算中使用了非状态变量可能出错 可能遗漏依赖项导致状态过期

详细说明

1. 依赖跟踪机制

  • 写法一derivedStateOf 会自动跟踪在其计算函数中访问的任何状态(listtargetCount)。当这些状态变化时,它会自动重新计算。

  • 写法二 :依赖项必须显式声明在 remember 的键中。如果遗漏了某个依赖项,当该依赖变化时不会触发重新计算。

2. 对象生命周期

kotlin 复制代码
// 写法一 - 只创建一次
val derivedState = remember {
    derivedStateOf { /* ... */ } // 只执行一次
}

// 写法二 - 依赖变化时重新创建
val derivedState = remember(key1, key2) {
    derivedStateOf { /* ... */ } // 当 key1 或 key2 变化时重新执行
}

3. 性能影响

  • 写法一 更高效,因为 derivedStateOf 实例只创建一次
  • 写法二 在依赖变化时会有额外开销:
    1. 创建新的 derivedStateOf 实例
    2. 丢弃旧实例
    3. 注册新的状态监听

4. 正确性考虑

两种写法在计算逻辑上结果相同,但写法二需要确保:

  1. 包含所有必要的依赖项
  2. 依赖项是稳定类型(避免不必要的重新创建)

何时使用哪种写法

✅ 优先使用写法一的情况:

  • 当派生状态只依赖于可组合函数内的状态时
  • 当依赖项是稳定的状态对象时
  • 大多数常见场景

⚠️ 考虑写法二的情况:

  1. 依赖外部参数时

    kotlin 复制代码
    @Composable
    fun FilteredList(externalParam: Int) {
        val list = // ...
        val filteredList by remember(list, externalParam) {
            derivedStateOf { 
                list.filter { it.length >= externalParam }
            }
        }
    }
  2. 依赖不稳定对象时

    kotlin 复制代码
    data class FilterCriteria(val minLength: Int) // 非稳定类型
    
    val filteredList by remember(criteria) {
        derivedStateOf { 
            list.filter { it.length >= criteria.minLength }
        }
    }

最佳实践建议

  1. 默认使用写法一

    kotlin 复制代码
    // 推荐 - 简洁高效
    val result by remember {
        derivedStateOf { computeValue(stateA, stateB) }
    }
  2. 当依赖外部变量时使用写法二

    kotlin 复制代码
    val result by remember(externalVar) {
        derivedStateOf { compute(state, externalVar) }
    }
  3. 避免不必要的重新创建

    kotlin 复制代码
    // 不推荐 - 依赖项变化会导致不必要的重新创建
    val result by remember(list, count) {
        derivedStateOf { /* 简单计算 */ }
    }
  4. 复杂派生状态使用自定义记忆

    kotlin 复制代码
    val complexResult by remember {
        derivedStateOf {
            // 计算密集型操作
            heavyComputation(list, count)
        }
    }

可视化重组行为

假设 list 变化但 targetCount 不变:

  • 写法一 :只重新计算 derivedStateOf,不重新创建对象
  • 写法二 :重新创建 derivedStateOf 对象并重新计算

结论

derivedStateOf 只能监听block内的State,一个非State类型数据的变化则可以通过remember的key进行监听

大多数情况下,写法一(无显式依赖项)是更好的选择

  • 更简洁
  • 更高效(避免不必要的对象创建)
  • 自动跟踪所有依赖
  • 符合 Compose 的设计哲学

仅当派生状态依赖外部参数或不稳定对象时,才需要使用写法二(显式依赖项)。在代码实践中,优先选择写法一可以创建更高效、更易维护的 Compose 应用。

相关推荐
_一条咸鱼_21 小时前
Android Runtime直接内存管理原理深度剖析(73)
android·面试·android jetpack
_一条咸鱼_2 天前
Vulkan入门教程:源码级解析
android·面试·android jetpack
_一条咸鱼_2 天前
Android Runtime内存共享与访问控制原理剖析(71)
android·面试·android jetpack
刘龙超3 天前
如何应对 Android 面试官 -> 玩转 Jetpack Room
android jetpack
刘龙超4 天前
如何应对 Android 面试官 -> 玩转 Jetpack ViewModel
android jetpack
alexhilton6 天前
为什么你的App总是忘记所有事情
android·kotlin·android jetpack
刘龙超6 天前
如何应对 Android 面试官 -> 玩转 Jetpack DataBinding
android jetpack
雨白6 天前
Jetpack系列(四):精通WorkManager,让后台任务不再失控
android·android jetpack
刘龙超7 天前
如何应对 Android 面试官 -> 玩转 JetPack ViewBinding
android jetpack
顾林海7 天前
ViewModel 销毁时机详解
android·面试·android jetpack