如何制作网站页面,中国最大的外贸平台,桂平网页设计,wordpress 批量打印文章Compose 附带效应
a. 纯函数
纯函数指的是函数与外界交换数据只能通过函数参数和函数返回值来进行#xff0c;纯函数的运行不会对外界环境产生任何的影响。比如下面这个函数#xff1a;
fun Add(a : Int, b : Int) : Int {return a b
}
“副作用”#xff08;side effe…Compose 附带效应
a. 纯函数
纯函数指的是函数与外界交换数据只能通过函数参数和函数返回值来进行纯函数的运行不会对外界环境产生任何的影响。比如下面这个函数
fun Add(a : Int, b : Int) : Int {return a b
}
“副作用”side effect指的是如果一个操作、函数或表达式在其内部与外界进行了互动产生运算以外的其他结果则该操作或表达式具有副作用。
最典型的情况就是修改了外部环境的变量值。例如如下代码Add() 函数执行它需要一个外部变量 a先进行 操作然 a b 返回。只要这个函数一执行外部变量 a 就会改变。而对于这个 a 所产生的改变这个就叫做副作用。
var a
fun Add(b : Int) : Unit{areturn a b
}
因此组合函数也是一个函数那么它也分为有副作用的和没副作用的。而组合函数的副作用和其它函数还有一些差异。
组合函数的特点
a. 执行顺序不定b. 可以并行运行c. 可能会非常频繁地运行
处理副作用
虽然我们不希望函数执行中出现副作用但现实情况是有一些逻辑只能作为副作用来处理。例如一些 IO 操作、计时、日志埋点等这些都是会对外界或收到外界影响的逻辑不能无限制的反复执行。所以 Compose 需要能够合理地处理一些副作用。 副作用的执行时机是明确的例如在 Recomposition 时等。 副作用的执行次数是可控的不应该随着函数反复执行。 副作用不会造成泄漏例如对于注册要提供适当的时机取消注册。
组合函数的副作用
组合函数是主要是用来做 UI 声明的、描述的只要你在可组合函数内做了与 UI 描述不相关的操作这一类操作其实都属于副作用。
在 Compose 中可组合函数内部理应只做视图相关的事情而不应该做函数返回之外的事情如访问文件等如果有那这就叫做附带效应以下操作全部都是危险的附带效应 写入共享对象的属性 更新 ViewModel 中的可观察项。 更新共享偏好设置。
可组合函数应该是无副作用的但是如果我们要在 Compose 里面使用可组合函数而且会产生附带效应这时就需要使用 EffectAPI以便以可预测的方式执行那些副作用。一个 effect就是一个可组合函数这个可组合函数不生成 UI而是在组合完成时产生副作用。
组合函数的生命周期
这些 Effect API 是与我们组合函数的生命周期相关联的。可组合项的生命周期比 activity 和 fragment 的生命周期更简单一般是进入组合、执行0次或者多次重组、退出组合。 Enter挂载到树上首次显示。 Composition重组刷新 UI。 Leave从树上移除不再显示。 组合函数中没有自带的生命周期函数想要监听其生命周期需要使用 Effect附带效应API LaunchedEffect第一次调用 Compose 函数的时候调用。 DisposableEffect内部有一个 onDispose() 函数当页面退出时调用。 SideEffectcompose 函数每次执行都会调用该方法。 LaunchedEffect
如果在可组合函数中进行耗时操作副作用往往都是耗时操作例如网络请求、I/O等就需要将耗时操作放入协程中执行而协程需要在协程作用域中创建因此 Compose 提供了 LaunchedEffect 用于创建协程。 当 LaunchedEffect 进入组件树时会启动一个协程并将 block 放入该协程中执行。 当组合函数从视图树上 detach 时协程还未被执行完毕该协程也会被取消执行。 当 LaunchedEffect 在重组时其 key 不变那 LaunchedEffect 不会被重新启动执行 block。 当 LaunchedEffect 在重组时其 key 发生了变化则 LaunchedEffect 会执行 cancel 后再重新启动一个新协程执行 block。
示例LaunchedEffect 在初次进入组件树时就会启动一个协程调用 block 块执行 1. LaunchedEffectSample.kt
Composable
fun ScaffoldSample(state : MutableStateBoolean,scaffoldState : ScaffoldState rememberScaffoldState()
){// TODO 当我启动这个应用时组件一开始加载进来LaunchedEffect() 就会启动一个协程执行它的 block 块//TODO 当 key state.value 发生改变时点击按钮时改变就会启动协程LaunchedEffect(state.value){// 开启一个弹窗TODO 是一个 block 块scaffoldState.snackbarHostState.showSnackbar(// 弹窗内容message Error message,actionLabel Retry message)}// TODO 脚手架Scaffold(scaffoldState scaffoldState,// 顶部标题栏区域topBar {TopAppBar(title { Text(text 脚手架示例)})},// 屏幕内容区域content {Box(modifier Modifier.fillMaxSize(), // 填充父容器contentAlignment Alignment.Center // 居中){Button(onClick {//TODO 点击按钮时弹窗改变 state 的值。一个动画效果为耗时操作即附带效应state.value !state.value}) {Text(text Click it!)}}})
}Composable
fun LaunchedEffectSample(){val state remember { mutableStateOf(false) }ScaffoldSample(state)
}
2. MainActivity.kt
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {LaunchedEffectSample()}}}
}
上面的示例中当我们启动 App 时就会让 LaunchedEffect 进入组件树时启动一个协程并将 block 放入该协程中执行。可以做如下改变让进入 App 时不执行 block 块。修改 LaunchedEffect 代码如下 if(state.value){LaunchedEffect(scaffoldState.snackbarHostState){// 开启一个弹窗TODO 是一个 block 块scaffoldState.snackbarHostState.showSnackbar(// 弹窗内容message Error message,actionLabel Retry message)}}
rememberCoroutineScope
由于 LauncedEffect 本身就是个可组合函数因此只能在其他可组合函数中使用。想要在可组合项外启动协程且需要对这个协程存在作用域限制以便协程在退出组合后自动取消可以使用 rememberCoroutineScope。
此外如果需要手动控制一个或多个协程的生命周期请使用 rememberCoroutineScope。拿到协程的作用域。
示例 1. RememberCoroutineScopeSample.kt
Composable
fun ScaffoldSample(){val scaffoldState rememberScaffoldState()// TODO 拿到协程作用域启动多个协程val scope rememberCoroutineScope()Scaffold(scaffoldState scaffoldState,//TODO 左侧抽屉栏点击了菜单按钮时弹出drawerContent {Box(modifier Modifier.fillMaxSize(),contentAlignment Alignment.Center) {Text(text 抽屉组件中的内容)} },// 顶部标题栏区域topBar {TopAppBar(// 左上角的菜单栏按钮点击后左侧弹窗navigationIcon {IconButton(onClick {// TODO 点击菜单按钮时弹出左侧抽屉栏// TODO 1 启动一个协程scope.launch {// 以动画的形式打开这个抽屉scaffoldState.drawerState.open()}}) {// 菜单按钮Icon(imageVector Icons.Filled.Menu, contentDescription null)}},title { Text(text 脚手架示例)})},// 屏幕内容区域content {Box(modifier Modifier.fillMaxSize(),contentAlignment Alignment.Center) {Text(text 屏幕内容区域)}},// TODO 右下角的悬浮按钮floatingActionButton {ExtendedFloatingActionButton(text { Text(text 悬浮按钮) },onClick {// TODO 2 启动一个协程scope.launch {// 弹窗scaffoldState.snackbarHostState.showSnackbar(点击了悬浮按钮)}})})
}
2. MainActivity.kt
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {//LaunchedEffectSample()ScaffoldSample()}}}
}
在上面代码中我们通过 val scope rememberCoroutineScope() 拿到协程作用域以此来控制多个协程生命周期 rememberUpdatedState
如果 key 值有更新那么 LaunchedEffect 在重组时就会被重新启动。但是有时候需要在 LaunchedEffect 中使用最新的参数值但是又不想重新启动 LaunchedEffect此时就需要用到 rememberUpdatedState。
rememberUpdatedState 的作用是给某个参数创建一个引用来跟踪这些参数并保证其值被使用时是最新值参数被改变时不重启 effect。
示例RememberUpdatedStateSample.kt
Composable
fun LandingScreen(onTimeOut : () - Unit){// TODO onTimeOut() 转换成一个状态了val currentOnTimeout by rememberUpdatedState(newValue onTimeOut)//TODO key1 Unit 表示这个 key 值不会变LaunchedEffect(key1 Unit){Log.d(HL, LaunchedEffect)repeat(10){delay(1000)Log.d(HL, delay ${it 1}s)}////onTimeOut()currentOnTimeout()}
}Composable
fun RememberUpdatedStateSample(){val onTimeOut1 : () - Unit { Log.d(HL, landing timeout 1) }val onTImeOut2 : () - Unit { Log.d(HL, landing timeout 2) }// 创建一个 state, 默认值为 onTimeOut1val changeOnTimeOutState remember { mutableStateOf(onTimeOut1) }Column {Button(onClick {// TODO 点击按钮时改变 changeOnTimeOutState 的值if(changeOnTimeOutState.value onTimeOut1){changeOnTimeOutState.value onTImeOut2}else{changeOnTimeOutState.value onTimeOut1}}) {Text(text choose onTimeOut ${if(changeOnTimeOutState.value onTimeOut1) 1 else 2})}//TODO changeOnTimeOutState.value OnTimeOut1 / OnTimeOut2LandingScreen(changeOnTimeOutState.value)}
}
DisposableEffect
DisposableEffect 也是一个可组合函数当 DisposableEffect 在其 key 值变化或者组合函数离开组件树时会取消之前启动的协程并会在取消协程前调用其回收方法进行资源回收相关的操作可以对一些资源等进行清理。
示例当开关按钮打开时拦截返回按钮。 DisposableEffectSample.kt
// 对返回进行一个拦截
Composable
fun BackHandler(backDispatcher : OnBackPressedDispatcher,onBack : () - Unit
){// onBack 包装成一个状态, TODO 以便可以随时替换为其它的函数val currentOnBack by rememberUpdatedState(newValue onBack)val backCallback remember {object : OnBackPressedCallback(true){override fun handleOnBackPressed() {//onBack()currentOnBack()}}}DisposableEffect(key1 backDispatcher){// 开关打开添加拦截 backCallbackbackDispatcher.addCallback(backCallback)// 执行时机为BackHandler 从组件树中移除也就是 switch 开关关掉的时候onDispose {Log.d(HL, onDispose)// 开关一关从组件树中移除backCallback.remove()}}
}Composable
fun DisposableEffectSample(backDispatcher : OnBackPressedDispatcher){// TODO 设置一个状态var addBackCallback by remember { mutableStateOf(false) }Row {// 开关按钮Switch(checked addBackCallback, // 默认选中或不选中onCheckedChange {// 当点击开关进行切换的时候调用这里的代码addBackCallback !addBackCallback})Text(text if (addBackCallback) Add back callback else Not add back callback)}if(addBackCallback){ // TODO 打开开关BackHandler() 执行BackHandler(backDispatcher){Log.d(HL, onBack)}}
}
MainActivity.kt
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {JetpackComposeSideEffectsTheme {//LaunchedEffectSample()//ScaffoldSample()//RememberUpdatedStateSample()DisposableEffectSample(onBackPressedDispatcher)}}}
}
SideEffect
SideEffect 是简化版的 DisposableEffectSideEffect 并未接收任何 key 值所以每次重组就会执行其 block。当不需要 onDispose、不需要参数控制时使用 SideEffect。SideEffect 主要用来与非 Compose 管理的对象共享 Compose 状态。
SideEffect 在组合函数被创建并载入视图树后才会被调用。
例如我们的分析库可能允许通过将自定义元数据在此示例中为“用户属性”附加到所有后续分析事件来细分用户群体。如需将当前用户的用户类型传递给你的分析库请使用 SideEffect 更新其值。 prodeceState
produceState 可以将非 Compose如 Flow、LiveData 或 RxJava状态转换为 Compose 状态。它接收一个 lambda 表达式作为函数体能将这些入参经过一些操作后生成一个 State 类型变量并返回。 produceState 创建了一个协程但它也可用于观察非挂起的数据源。 当 produceState 进入 Composition 时获取数据的任务被启动当其离开 Composition 时该任务被取消。 derivedStateOf
如果某个状态是从其它状态对象计算或派生得出的请使用 derivedStateOf。作为条件的状态我们称为条件状态。当任意一个条件状态更新时结果状态都会重新计算。 snapshotFlow
使用 snapshotFlow 可以将 State 对象转换为 Flow。snapshotFlow 会运行传入的 block并发出从块中读取的 State 对象的结果。当在 snapshotFlow 块中读取的 State 对象之一发生变化时如果新值与之前发出的值不相等Flow 会向其收集器发出新值。