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 应用。

相关推荐
雨白2 天前
Hilt 入门指南:从 DI 原理到核心用法
android·android jetpack
我命由我123452 天前
Android 开发 - Android JNI 开发关键要点
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton3 天前
在Jetpack Compose中创建CRT屏幕效果
android·kotlin·android jetpack
峰哥的Android进阶之路3 天前
viewModel机制及原理总结
android jetpack
我命由我123454 天前
Android WebView - loadUrl 方法的长度限制
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Coffeeee4 天前
面试被问到Compose的副作用不会,只怪我没好好学
android·kotlin·android jetpack
Frank_HarmonyOS7 天前
Android APP 的压力测试与优化
android jetpack
QING6188 天前
Jetpack Compose 条件布局与 Layout 内在测量详解
android·kotlin·android jetpack
Lei活在当下9 天前
【现代 Android APP 架构】09. 聊一聊依赖注入在 Android 开发中的应用
java·架构·android jetpack
bqliang9 天前
Jetpack Navigation 3:领航未来
android·android studio·android jetpack