今天大概会聊到的范围
- recompose
在整个系列文章中,有提过不只一次的 recomposition。在 Day 15、16 时有特别提过抽象概念上是如何运作的。但通常,我都把它当成一个直觉的认知、一个常识,好像 recomposition 就是会发生、就是会如我们想像中执行。
但实际上,什麽时候会触发 recomposition、它的机制又是怎麽运作呢?让我们从一个范例开始看看:
@Composable
fun LogComp(name: String, value: Int = 0, content: @Composable () -> Unit) {
Log.d(TAG, "LogComp: name = $name, value = $value") // <-- 1
content()
}
@Composable
fun BuzzComp() {
var v by remember { mutableStateOf(0) }
Column {
LogComp("LogComp1") {
Log.d(TAG, "BuzzComp: log inside LogComp1")
LogComp("LogComp2") {
Log.d(TAG, "BuzzComp: log inside 2")
LogComp(name = "Third", value = v) {
Log.d(TAG, "BuzzComp: log inside third")
}
}
}
Button(onClick = { v++ }) {
Log.d(TAG, "BuzzComp: log inside button")
Text("Change value")
}
}
}
LogComp
来记录 composable function 被呼叫的时间。一但被呼叫 LogCat 就会纪录v
LogComp
彼此包装彼此。在每一个 LogComp
的 content lambda 中,除了下一层的 LogComp
之外,还包含一个 LogCat logLogComp
放入 State v
v
+1请问,在点击後会发生什麽事情呢?哪些 function 会被呼叫到呢?
--- init (composition) ---
LogComp: name = LogComp1, value = 0
BuzzComp: log inside LogComp1
LogComp: name = LogComp2, value = 0
BuzzComp: log inside LogComp2
LogComp: name = LogComp3, value = 0
BuzzComp: log inside LogComp3
BuzzComp: log inside button
--- clicked (trigger recomposition) ---
BuzzComp: log inside second
LogComp: name = LogComp3, value = 1
--- end ---
你猜对了吗? 只有 second LogComp 的 content lambda 和 LogComp 3 本身会被呼叫到。
简单来说,有对 State 的 value 有 access 的地方,当 value 改变时都会触发 recomposition。
LogComp2
的 content lambda 要呼叫 LogComp3
时,有取用 v 并提供给 LogComp3
LogComp3
本身也有取用 value 来放在 Log 和 Text
中,所以会需要 recompositionRecompose scope 是 Compose 用来记录哪些东西需要被 "重新整理" 的区块划分,它是每次触发 recompose 的最小单位。他会纪录哪些区块有取用哪些 State。
在 Day 16 有提到,每个 Composable 前後会偷偷被加上 start / end 来将这个 composable 纪录在中 Gap Buffer 中。其实那就是一个 Recompose Scope。每个回传 Unit 的 non-inline composable function ,都会视为一个 recompose scope。
因此,每个 LogComp、Button、Text 甚至 LogComp 的 content lambda 都会产生一个 recompose scope。
当 State 改变时,有用到该 State 的 recompose scope 都会被 invalidate。并且会在下一个 frame 前执行 composition。
因此,LogComp3
本身有用到 State 所以会触发 recomposition。LogComp2
的 content lambda 也有取用该 State ( 要给 LogComp3
) 所以也会跟着触发。LogComp2
和 LogComp2
的 content lambda 是不同的 recompose scope,因此不会触发 recompose。
LogComp2
如果我们将 value 使用的人从 LogComp3
改成 LogComp2
呢?
@Composable
fun BuzzComp() {
var v by remember { mutableStateOf(0) }
Column {
LogComp("LogComp1") {
Log.d(TAG, "BuzzComp: log inside LogComp1")
LogComp("LogComp2", value = v) { // <--- 换 LogComp2 用值
Log.d(TAG, "BuzzComp: log inside 2")
LogComp(name = "Third") {
Log.d(TAG, "BuzzComp: log inside third")
}
}
}
Button(onClick = { v++ }) {
Log.d(TAG, "BuzzComp: log inside button")
Text("Change value")
}
}
}
--- init (composition) ---
LogComp: name = LogComp1, value = 0
BuzzComp: log inside LogComp1
LogComp: name = LogComp2, value = 0
BuzzComp: log inside 2
LogComp: name = Third, value = 0
BuzzComp: log inside third
BuzzComp: log inside button
--- clicked (trigger recomposition) ---
BuzzComp: log inside LogComp1
LogComp: name = LogComp2, value = 1
--- end ---
一样只有 LogComp1 的 content lambda 和 LogComp2 本身触发 recompose。
在第二个范例中,即便 LogComp3
是放在 LogComp2
的 content lambda 中, LogComp2
的 content lambda 又在 LogComp2
function 中会被呼叫到。但实际上,因为 LogComp2
的 content lambda 与 LogComp3
和 LogComp2
属於不同的 recompose scope 所以不会触发 recomposition、不会再次被呼叫。
由此可知,compose 中的 function 行为其实和我们直觉想像的不尽相同。这又再次证实了为什麽不应该在 Composable function 中插入非 compose 相关的行为。因为我们无法确保这些 function 的触发时机。反之,我们应该将这些 side effect 放到外部,或是适当的 composable function ( such as SideEffect
, DisposableEffect
.. etc )
Reference:
>>: Re: 新手让网页 act 起来: Day23 - useCallback 与 React.memo
上午: Python程序设计 延续昨日课程,今日从流程控制开始,课程中老师也有出几个练习题让同学试着...
GitHub 目前提供的 Project 功能为 Board (看板),在撰写这篇文章时,GitH...
资安制度说白了就是企业或机构营运的日常管理,套用 ISMS 框架也只是为了满足内部或外部需求,视产业...
我们今天来介绍一份View的基本结构 以昨日的Form作为例子: <odoo> <...
电源是一种向电力负载提供电力的电气设备。电源的主要功能是将电流从源头转换成正确的电压、电流和频率,为...