Compose 两种 `derivedStateOf` 写法比较

484 阅读3分钟

在 Jetpack Compose 中,derivedStateOf 用来将一个或多个State转成另一个State。derivedStateOf{...} 的block中可以依赖其他State创建并返回一个DerivedState,当block中依赖的State发生变化时,会更新此DerivedState,依赖此DerivedState的所有Composable会因其变化而重组。

代码对比

写法一(无依赖项)

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

写法二(显式依赖项)

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. 对象生命周期

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

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

3. 性能影响

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

4. 正确性考虑

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

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

何时使用哪种写法

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

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

⚠️ 考虑写法二的情况:

  1. 依赖外部参数时

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

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

最佳实践建议

  1. 默认使用写法一

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

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

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

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

可视化重组行为

假设 list 变化但 targetCount 不变:

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

结论

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

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

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

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