文章目录
官方文档
声明性编程范式
- widget的实现是,修改view的属性,继而影响 UI,刷新/重绘界面
在传统的 Android UI 开发中,界面和数据是分离的,你需要手动去"命令" UI 进行修改。这很容易导致状态不同步的 Bug。
痛点:随着界面越来越复杂,你要记住 "这个数据的变化,引起的每一处需要更新的 View,一旦漏写了一句,界面就会显示错误"
- 声明性界面模型中,不能直接获取到某个ui组件,再修改它的属性。而是通过声明 不同 State (状态) 的 UI,在 修改 State 后,发生 可组合项的重组,并刷新界面。会智能重组,某些可组合项会跳过重组。
原文:Compose 会智能地选择在任何给定时间需要重新绘制界面的哪些部分
核心公式:UI = f(State)
声明性 UI 的核心可以用一个数学公式表示:UI = f(State)
State (状态):应用的数据。
f (函数):你的 Compose UI 代码。
UI (界面):最终屏幕上显示的内容。
这意味着:
界面只是当前状态在某一时刻的视觉快照。
你不能直接去修改 UI(比如 tv.text = "新文字" 这种做法在 Compose 里是不存在的)。
你要做的仅仅是修改状态(State)。一旦状态改变,Compose 就会带着新的状态重新运行那个函数 f(即重组 Recomposition),从而画出新的 UI。
可组合项,重组是智能的
可能会频繁、并行、无序地 进行重组,也可能会智能跳过某些可组合项的重组。
不应在 可组合函数 展示 UI 元素的地方,利用重组的附带效应(Side Effect),执行文件读写、网络访问等操作。
附带效应:可组合项被框架,智能重组调用的时机、次数 及是否会调用都是不受人为控制的,那在重组过程中 频繁执行 io密集/cpu密集操作 都是不可取的
若一定要执行 这些耗时操作,也行的。可以在 LaunchedEffect(key) { } 这个协程作用域中执行。它会执行多少次呢?
LaunchedEffect 的执行次数完全取决于你传给它的 key(键值)。
它的核心运行规则是:
- 第一次进入界面时(进入组合区),必定执行 1 次。
- 在随后的 UI 重组中,只有当 key 发生变化时,才会先取消上一次正在执行的任务,然后重新执行 1 次。
- 当它所在的 UI 节点从屏幕上消失时(离开组合区),它内部的协程会被自动取消。
根据你传入的 key 的不同,通常有以下两种典型场景:
场景 1 :只执行 1 次(常用于初始化)
如果你希望某个操作在整个 UI 生命周期内只执行一次,无论界面重组多少次都不再执行,你可以传入一个常量(通常是 Unit 或 true)。
kotlin
@Composable
fun MyScreen() {
// 传入 Unit 作为 key
LaunchedEffect(Unit) {
// 这里的代码只会在 MyScreen 第一次显示时执行 1 次
// 即使 MyScreen 因为其他状态变化重组了 100 次,这里也不会再执行
fetchDataFromNetwork()
}
// UI 代码...
}
场景 2 :根据变量变化执行 N 次(常用于参数变化时的重新加载)
如果你希望当某个特定的状态或参数改变时,重新触发操作,你就把那个变量作为 key 传进去。
kotlin
@Composable
fun UserProfile(userId: String) {
// 将 userId 作为 key
LaunchedEffect(userId) {
// 1. 第一次进入界面时,执行 1 次获取用户信息
// 2. 如果重组时,userId 的值变了(比如从 "001" 变成了 "002"),
// Compose 会自动取消之前未完成的 "001" 请求,并重新执行 1 次获取 "002" 的请求。
// 3. 如果重组时,userId 没变,这里绝不会执行。
val user = api.getUser(userId)
}
}