• Android全新UI框架之Compose状态管理与重组


    Compose采取了声明式UI的开发范式。在这种范式中,UI的职责仅作为数据状态的反应。如果数据状态没有变化,则UI永远不会自行改变。如果把Composable的执行看作是一个函数运算,那么状态就是函数的参数,生成的布局就是函数的输出。

    Stateless和Stateful

    传统视图中通过获取组件对象句柄来更新组件状态,而Compose则通过重新执行Composable函数来更新页面(重组)。StatelessComposable只依赖参数的Composable;相对的,有些Composable内部持有或者访问了某些状态,称之为StatefulComposable。StatelessComposable的重组只能来自上层Composable的调用,而StatefulComposable的重组来自其以来的状态的变化。

    //Statelesscomposable
    @Composable
    fun  Hello(name:String){
        Text(text = "Hello $name")
    }
    
    //StatefulComposable
    @Preview
    @Composable
    fun CounterComponent() {
    
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
        //remember中计算得到的数据会自动缓存,当Composable重组再次执行到remember处会返回之前已缓存的数据,无须重新计算。mutableStateOf的调用一定要出现在remember中,不然每次重组都会创建新的状态。
            var counter by remember { mutableStateOf(0) }
    
            Text( //1
                "Click the buttons to adjust your value:",
                Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center
            )
    
            Text( //2
                "$counter",
                Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center,
                style = typography.h3
            )
    
            Row {
                Button(
                    onClick = { counter-- },
                    Modifier.weight(1f)
                ) {
                    Text("-")
                }
                Spacer(Modifier.width(16.dp))
                Button(
                    onClick = { counter++ },
                    Modifier.weight(1f)
                ) {
                    Text("+")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在Compose中使用State描述一个状态,泛型T是状态的具体类型。

    interface State<out T> {
        val value: T
    }
    
    • 1
    • 2
    • 3

    State是一个可观察对象。当Composabel对State的value进行读取时会与State建立订阅关系,当value发生变化时,作为监听者的Composable会自动重组刷新UI。
    有时候Composable需要对State的value进行修改,比如在CounterComponent中单击按钮需要修改counter的值,所以counter可被修改,使用MutableState来表示可修改状态,其包裹的数据是一个可修改的var类型。

    @Stable
    interface MutableState<T> : State<T> {
        override var value: T
        operator fun component1(): T
        operator fun component2(): (T) -> Unit
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    MutableState有三种用法:

    1. 创建MutableState

       var counter:MutableState<Int> = mutableStateOf(0)
      
      • 1
    2. 解构方式

        val(counter,setCounter) = mutableStateOf(0)
      
      • 1

      此时的counter已经是一个Int类型的数据,后续使用的地方可以直接访问,无须再使用点操作符获取value,而需要更新counter的地方可以使用setCounter(xx)完成。

    3. 属性代理
      使用by关键字直接获取Int类型counter。

      var counter by mutableStateOf(0) 
      
      • 1

      by关键字的原理是对counter的读写会通过getValue和setValue这两个运算符的重写最终代理为对value的操作。

    状态上提

    状态上提的通常做法是将内部状态移除,通过参数传入需要在UI显示的状态,以及需要回调给调用方的事件,案例如下所示:

    @Composable
    fun CounterComponent(
        counter:Int,//重组时调用方传入当前需要显示的计数
        onIncrement:()->Unit,//向调用方回调单击加号的事件
        onDecrement:()->Unit,//向调用方回调单击减号的事件
    ) {
    
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text( //1
                "Click the buttons to adjust your value:",
                Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center
            )
    
            Text( //2
                "$counter",
                Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center,
                style = typography.h3
            )
    
            Row {
                Button(
                    onClick = { onDecrement() },
                    Modifier.weight(1f)
                ) {
                    Text("-")
                }
                Spacer(Modifier.width(16.dp))
                Button(
                    onClick = { onIncrement() },
                    Modifier.weight(1f)
                ) {
                    Text("+")
                }
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    状态的持久化与恢复

    前面说到,remember可以缓存创建的状态,避免因为重组而丢失。使用remember缓存的状态虽然可以跨越重组,但是不能跨越Activity或跨越进程存在。如果想要更长久地保存状态,就需要使用到rememberSaveable了,它可以像Activity的onSaveInstanceState那样在进程被杀死时自动保存状态,同时onRestoreInstanceState一样随进程重建而自动恢复。
    rememberSaveable中的数据会随onSaveInstanceState进行保存,并在进程或Activity重建时根据key恢复到对应的Composable中,这个key就是Composable在编译期被确定的唯一标识。因此当用户手动退出应用时,rememberSavable中的数据才会被清空。
    rememberSaveable实现原理实际上就是将数据以Bundle的形式保存,所以凡是Bundle支持的基本数据类型都可以自动保存。对于一个对象类型,则可以通过添加@Parcelize变为一个Parcelable对象进行保存。当我们遇到有的数据结构可能无法添加Parcelable接口,此时可以通过自定义Saver为其实现保存和恢复的逻辑。只需要在调用rememberSaveable时传入此Saver即可:

    @Parcelize
    data class City(val name: String,val country:String):Parcelable
    
    object CitySaver:Saver<City,Bundle>{
        override fun restore(value: Bundle): City? {
            return value.getString("name")?.let {name ->
                value.getString("country")?.let { country->
                    City(name = name,country=country)
                }
            }
        }
    
        override fun SaverScope.save(value: City): Bundle? {
            return Bundle().apply {
                putString("name",value.name)
                putString("country",value.country)
            }
        }
    
    }
    
    @Preview
    @Composable
    fun WelcomePageLightPreview() {
        WelcomePage()
        val city = rememberSaveable(stateSaver = CitySaver) {
            mutableStateOf(City("City",""))
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    除了自定义Saver外,Compose也提供了MapSaver和ListSaver供开发者使用,MapSaver将对像转换为Map 的结构进行保存,注意value是可保存到Bundle的类型,同理,ListSaver则是将对像转换为List的数据结构进行保存。

    val cityMapSaver = run {
            val nameKey = "Name"
            val countryKey = "Country"
            mapSaver(
                save = { mapOf(nameKey to it.name,countryKey to it.country) },
                restore = {City(it[nameKey] as String,it[countryKey] as String)}
            )
        }
    
        val cityListSaver = run {
            val nameKey = "Name"
            val countryKey = "Country"
            listSaver(
                save = { listOf(it.name,it.country) },
                restore = {City(nameKey,countryKey)}
            )
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    使用ViewModel管理状态

    各位读者可以参考下面这两篇博客:
    https://juejin.cn/post/7105236042864656391
    https://blog.csdn.net/u010976213/article/details/117284079

    重组与自动刷新

    智能的重组

    Compose的重组非常“智能”,当重组发生时,只有状态发生更新的Composable才会参与重组,没有变化的Composable会跳过本次重组。

    避免重组的“陷阱”

    由于Composable在编译期代码会发生变化,代码的实际运行情况可能不如预期的那样。所以需要了解composable在重组执行时的一些特性,避免陷入重组的“陷阱”。

    1. Composable会以任意顺序执行
      多个composable函数未必按照先后顺序执行,因此不能在composable设置一个全局变量,以期望在第一个composable中修改该值,在第二个composable修改为另一个值。

    2. Composable会并发执行
      重组中的Composable并不一定执行在UI线程,它们可能在后台线程池中并行执行,这有利于发挥多核处理器的性能优势。

    3. Composable会反复执行
      除了重组会造成Composable的再次执行外,在动画等场景中每一帧的变化都可能引起Composable的执行,因此Composable有可能会短时间内反复执行,我们无法准确判断它的执行次数。

    4. Composable的执行是“乐观”的
      所谓“乐观”是指composable最终总会依据最新的状态正确地完成重组。在某些场景下,状态可能会连续变化,这可能会导致中间态的重组在执行中被打断,新的重组会插入进来。对于被打断的重组,composable不会将执行一般的重组结果反应到视图树上,因为他知道最后一次状态总归是正确的,因此中间态丢弃也没关系。

    总结:composable框架要求composable作为一个无副作用的纯函数运行,只要在开发中遵循这一原则,上述这一系列特性就不会成为程序执行的“陷阱”,反而有助于提高程序的执行性能。

    如何确定重组范围

    经过composable编译器处理后的Composable代码在对state进行读取的同时,能够自动建立关联,在运行过程中当state变化时,Compose会找到关联的代码块标记为Invalid。在下一渲染帧到来之前,Compose会触发重组并执行invalid代码块,Invalid代码块即下一次重组的范围。能够被标记为Invalid的代码必须是非inline且无返回值的composable函数或lambda。因为inline函数在编译期间会在调用处展开,因此无法在下次重组时找到合适的调用入口,只能共享调用方的重组范围。而对于有返回值的函数,由于返回值的变化会影响调用方,所以必须连同调用方一同参与重组,因此它不能单独作为Invalid代码块。

    优化重组的性能

    在编译期间,compose编译器会根据代码调用位置,为composable生成索引key,并存入composition。composable在执行过程中通过与key的对比,可以知道当前应该执行何种操作(增、删、更新、移动等多种变化)。

    生命周期与副作用

    compose的dsl很形象地描述了UI的视图结构,其背后对应这一视图树的结构体,我们称之为Composition。Composition在Composable初次执行时被创建,在Composable中访问State时,Composition记录其引用,当State变化时,Composition触发对应的Composable进行重组,更新视图树的节点,显示中的UI得到刷新。

    Composable的生命周期
    • OnActive(添加到视图树)
      即Composable被首次执行,在视图树上创建对应的节点。
    • OnUpdate(重组)
      Composable跟随重组不断执行,更新视图树上的对应节点。
    • OnDispose(从视图树移除)
      Composable不再被执行,对应节点从视图树上移除。
    Composable的副作用

    Composable在执行过程中,凡是会影响外界的操作都属于副作用,比如弹出Toast、保存本地文件、访问远程或本地数据等。我们知道,重组可能会造成Composable反复执行,副作用显然是不应该跟随重组反复执行的。为此,Compose提供了一系列副作用API,可以让副作用API只发生在Composable生命周期的特定阶段,确保行为的可预期性。

    1. DisposableEffect
      该api可以感知composable的onactive和ondispose,允许通过副作用完成一些预处理和收尾处理。DisposableEffect想rememner一样可以接受观察参数key,但是它的key不能为空。如果key为Unit或true这样的常量,则block只在onactive时执行一次;如果key为其他变量,则block在onactive以及参数变化时的onupdate中执行。DisposableEffect的最后必须跟随一个onDispose代码块,否则会编译错误。 onDispose常用来做一些副作用的收尾处理。当有新的副作用会执行onDispose,此外当Composable进入onDispose时,也会执行。

      @Composable
      fun DisposableEffectTest(
          lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
      ) {
          val inputText = remember { mutableStateOf("") }
          Log.e("DisposableEffectTest","Composed")
          DisposableEffect(inputText.value) {
              // Create an observer that triggers our remembered callbacks
              // for sending analytics events
              val observer = LifecycleEventObserver { _, event ->
                  if (event == Lifecycle.Event.ON_START) {
                      Log.e("DisposableEffectTest","ON_START")
                  } else if (event == Lifecycle.Event.ON_STOP) {
                      Log.e("DisposableEffectTest","ON_STOP")
                  }
              }
      
              // Add the observer to the lifecycle
              lifecycleOwner.lifecycle.addObserver(observer)
              // When the effect leaves the Composition, remove the observer
              onDispose {
                  Log.e("DisposableEffectTest","onDispose")
                  lifecycleOwner.lifecycle.removeObserver(observer)
              }
          }
          Scaffold() {
              Column(modifier = Modifier.padding(it)) {
                  Button(onClick = {
                      inputText.value = "按了一下"
                  }) {
                      Text(text = "按钮"+inputText.value)
                  }
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    2. SideEffect
      SideEffect在每次成功重组时都会执行,所以不能用来处理那些好事或者异步的副作用逻辑。因为SideEffect能够获取与Composable一致的最新状态,它可以用来将当前State正确地暴露外外部。

      @Composable
      fun rememberAnalytics(user: User): FirebaseAnalytics {
          val analytics: FirebaseAnalytics = remember {
              /* ... */
          }
      
          // On every successful composition, update FirebaseAnalytics with
          // the userType from the current User, ensuring that future analytics
          // events have this metadata attached
          SideEffect {//将状态通知外部
              analytics.setUserProperty("userType", user.userType)
          }
          return analytics
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    3. LaunchedEffect
      当副作用中有处理异步任务的需求时,可以使用LaunchedEffect。在Composable进入onactive时,LaunchedEffect会启动协程执行block中的内容,可以在其中启动子协程或调用挂起函数。当Composable进入OnDispose时,协程会自动取消,因此LaunchedEffect不需要实现OnDispose{…}。
      LaunchedEffect支持观察参数key的设置,当key发生变化时,当前协程自动结束,同时开启新协程。

      @Composable
      fun LaunchedEffectTest() {
          val state = remember {
              mutableStateOf("xiaomi")
          }
          LaunchedEffect(state){
              Log.e("LaunchedEffectTest", "request")
              delay(3000)//模拟网络操作
              state.value = "oppo"
          }
          Log.e("LaunchedEffectTest", state.value)
          Scaffold{
              Column(modifier = Modifier.padding(it)) {
                  Spacer(modifier = Modifier.padding(top = 50.dp))
                  Button(onClick = {
                      state.value = "vivo"
                  }) {
                      Text(text = "按钮")
                  }
                  Spacer(modifier = Modifier.padding(top = 100.dp))
                  Text(text = "手机品牌 ${state.value}")
              }
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    4. rememberCoroutineScope
      LaunchedEffect只能在Composable中调用,如果想在非Composable环境中使用协程,例如在Button的Onclick中使用协程,并希望其在OnDispose时自动取消,此时可以使用rememberCoroutineScope。

      @Composable
      fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {
      
          // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
          val scope = rememberCoroutineScope()
      
          Scaffold(scaffoldState = scaffoldState) {
              Column {
                  /* ... */
                  Button(
                      onClick = {
                          // Create a new coroutine in the event handler to show a snackbar
                          scope.launch {
                              //Thread.sleep(1000)
                              scaffoldState.snackbarHostState.showSnackbar("Something happened!")
                          }
                      }
                  ) {
                      Text("Press me")
                  }
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    5. rememberUpdatedState
      LaunchedEffect会在参数key变化时启动一个协程,但有时我们并不希望协程中断,只要能够实时获取最新状态即可,此时可以借助rememberUpdatedState实现。

      @Composable
      fun UpdatedStateTest() {
          var message= remember { mutableStateOf("start") }
          Scaffold { innerPadding ->
              Column(modifier = Modifier.padding(innerPadding)){
                  Button(
                      onClick = {
                          message.value = "clicked"
                      }
                  ) {
                      Text("描述信息")
                  }
                  LoadingScreen(message.value)
              }
          }
      }
      
      @Composable
      fun LoadingScreen(text: String,scaffoldState: ScaffoldState = rememberScaffoldState()) {
          val messageText by rememberUpdatedState(text)
          Log.e("LoadingScreen", "start")
          LaunchedEffect(true) {
              Log.e("LoadingScreen", "delay origin ${messageText}")
              delay(4000)
              Log.e("LoadingScreen", "delay remember ${messageText}")
              scaffoldState.snackbarHostState.showSnackbar(
                  message = "切换了方法",
                  actionLabel = messageText
              )
          }
          Scaffold(scaffoldState = scaffoldState) {
              Column(modifier = Modifier.padding(it)) {
                  
              }
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
    6. snapshotFlow
      LaunchedEffect中可以通过rememberUpdatedState获取最新状态,但是当状态发生变化时,LaunchedEffect无法第一时间受到通知,如果通过改变观察参数key来通知状态变化,则会中断当前执行中的任务,成本太高。简言之,LaunchedEffect缺少轻量级的观察状态变化的机制。

      @Composable
      fun SnapshotFlow() {
          Box(modifier = Modifier.fillMaxSize(),
              contentAlignment = Alignment.Center) {
              val listState = rememberLazyListState()
              LazyColumn(state = listState) {
                  items(500) { index ->
                      Text(text = "Item: $index")
                  }
              }
              Log.e("SnapshotFlow", "Recompose")
              LaunchedEffect(listState) {
                  snapshotFlow { listState.firstVisibleItemIndex }
                      .map { index -> index > 4 }
                      .distinctUntilChanged()
                      .filter { it }
                      .collect {//经过snapshotFlow 转换的flow是个冷流,只有在collect之后,block才开始执行。
                          Log.e("SnapshotFlow", "snapshotFlow${it}")
                      }
              }
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      当一个LaunchedEffect中依赖的State会频繁变化时,不应该使用State的值作为key,而应该将State本身作为key,然后在其内部使用snapshotFlow 依赖状态。使用state作为key是为了当state对象本身变化时重启副作用。

    7. produceState
      produceState会启动一个协程,和SideEffect相反,使用此协程可以将非Compose状态转换为Compose状态。

      @Composable
      fun loadNetworkImage(
          url: String,
          imageRepository: ImageRepository
      ): State<Result<ImageBitmap>> {
          Log.e("ProduceStateExample", "loadNetworkImage: invoke" )
          //当url和imageRepository发生变化时,producer会重新执行
          return produceState<Result<ImageBitmap>>(initialValue = Result.Loading,url, imageRepository) {
      //        value = Result.Loading
              val image = imageRepository.loadNetworkImage(url)
              //value 为 MutableState 中的属性
              value = if (image == null) {
                  Result.Error
              } else {
                  Result.Success(image)
              }
          }
      }
      
      //密封类
      sealed class Result<T>() {
          object Loading : Result<ImageBitmap>()
          object Error : Result<ImageBitmap>()
          data class Success(val image: ImageBitmap) : Result<ImageBitmap>()
      }
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
    8. derivedStateOf
      derivedStateOf用来将一个或多个State转成另一个State。derivedStateOf{…}的block中可以依赖其他State创建并返回一个DerivedState,当block依赖的State发生变化时,会更新此DerivedState,依赖此DerivedState的所有Composable会因其变化而重组。

      @Composable
      fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) {
          val todoTasks = remember { mutableStateListOf<String>("huawei", "xiaomi", "oppo", "apple", "Compose") }
          // 选择 todoTasks中 属于 highPriorityKeywords 的部分
          val highPriorityTasks by remember(highPriorityKeywords) {
              derivedStateOf { todoTasks.filter { highPriorityKeywords.contains(it) } }
          }
          Log.e("TodoList", "todoTasks:${todoTasks.toList().toString()}" )
          Log.e("TodoList", "highPriorityTasks:${highPriorityTasks.toList().toString()}" )
          Column(modifier = Modifier.fillMaxWidth()) {
              LazyColumn {
                  item {
                      Text(text = "add-TodoTasks", Modifier.clickable {
                          todoTasks.add("Review")
                      })
                  }
      
                  item {
                      Divider(
                          color = Color.Red, modifier = Modifier
                              .height(2.dp)
                              .fillMaxWidth()
                      )
                  }
                  items(highPriorityTasks) { Text(text = it) }
                  item {
                      Divider(
                          color = Color.Red, modifier = Modifier
                              .height(2.dp)
                              .fillMaxWidth()
                      )
                  }
                  items(todoTasks) {
                      Text(text = it)
                  }
              }
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39

      derivedStateOf只能监听block内的state,一个非state类型数据的变化则可以通过remember的key进行监听,如上例所示。
      注意:在statefulcomposable中创建状态时,需要使用remember包裹,状态只在onactive时创建一次,不跟随重组反复创建,所以remember本质上也是一种副作用api。

    9. 副作用API的观察参数
      不少副作用api都允许指定观察参数key。当观察参数变化时,执行中的副作用会终止,key的频繁变化会影响执行效率。反之,如果副作用中存在可变值,但没有指定为key,有可能因为没有及时响应变化而出现bug。因此,关于参数key的添加可以遵循以下原则:当一个状态的变化需要造成副作用终止时,才将其添加为观察参数key,否则应该将其使用rememberUpdatedState包装后,在副作用中使用,以避免打断执行中的副作用。

  • 相关阅读:
    【C语言】如何去求最大公约数和最小公倍数?
    mybatis学习(15):mybatis连接mysql数据库
    数据结构与算法(LeetCode) 第二节 链表结构、栈、队列、递归行为、哈希表和有序表
    基于神经网络的柯氏音血压计
    使用Python调用API接口获取淘宝商品数据
    如何使用反 CSRF 令牌保护您的网站和 Web 应用程序
    Spring Security使用总结八,Security的第二个功能授权,不同的角色访问不同的资源
    启动mysql报错The server quit without updating PID file
    Python面向对象编程之对象行为与特殊方法
    使用Pytorch从零实现Vision Transformer
  • 原文地址:https://blog.csdn.net/qq_36828822/article/details/136256214