Kotlin 函数作用域 - 深入理解 Compose 中的 DisposeEffect 副作用函数
前段时间一直在忙鸿蒙相关的工作,最近忙里抽闲,总结一下之前在写 ComposeHooks 项目的一些小小心得。
DSL,其实在这里更多指的是利用作用域概念,限定函数闭包内的函数调用行为(下面称之为作用域内行为)。
在之前的文章:# 在 Kotlin 中巧妙的使用 DSL 封装 SpannableStringBuilder
提到了编写 DSL 的一些小心得,总结如下:
期望一个闭包得内部行为,编写相应的作用域接口:
1 | interface 作用域名称接口 { |
这里得作用域名称接口建议通过 XxxScope
这种格式命名。
我们回到 Compose 源码 - DisposeEffect
中继续学一下,看看 Compose 团队是如何使用这种编码技巧的:
1 |
|
这里关注两个类型:DisposableEffectScope
是这里的作用域,DisposableEffectResult
是副作用 effect 函数的返回值,他们的源码如下:
1 | class DisposableEffectScope { |
DisposableEffectScope
作用域不是一个接口而是一个具体的类,其中只有一个内联函数 onDispose
,也就是说在我们的 DisposableEffect
闭包中仅可以调用这一个作用域内行为
同时由于 effect 副作用函数 effect: DisposableEffectScope.() -> DisposableEffectResult
要求必须返回 DisposableEffectResult
,这就实际上规范了我们必须要在最后一行调用这个内联函数 onDispose
(因为其返回值也正是我们副作用函数限定的返回值类型DisposableEffectResult
)。
DisposableEffectResult
实际上是对 onDispose
函数接收的闭包的一个包装类型!
我们传入的闭包函数是通过DisposableEffectImpl
这个类实现的相关重组逻辑,他其实是RememberObserver
的一个实现类。
1 | //compose内部私有的作用域实例 |
第一步:在 onRemembered
执行时我们传入的闭包effect
会被执行(此时他的作用域,或者说函数的接收者是InternalDisposableEffectScope
),还记得这个副作用 effect 函数的签名是什么吗?
答案: effect: DisposableEffectScope.() -> DisposableEffectResult
也就说我们闭包内的代码会执行,通过 onDispose
函数创建的DisposableEffectResult
实例缓存到:var onDispose
。
这里实际缓存的也就是 onDispose
函数接收的卸载时执行的闭包函数。
第二步:当我们组件卸载时,记住的内容被忘记,onForgotten
回调执行。此时会调用var onDispose
中的 dispose
函数。
还记得这个函数会做什么吗?
1 | inline fun onDispose( |
也就是实际在执行我们写在 onDispose
函数中的卸载执行的闭包
再次加深记忆:
在 DSL 这种编码模式下,我们需要:
确定作用域内行为,对应抽象成类、接口;
1
2
3
4
5
6
7// 入口
fun TextView.buildSpannableString(init: DslSpannableStringBuilder.() -> Unit)
//作用域内的行为声明
interface DslSpannableStringBuilder {
//增加一段文字
fun addText(text: String, method: (DslSpanBuilder.() -> Unit)? = null)
}如果一个行为必须要被执行,我们可以设置一个特殊的
XxxResult
类型,要求作用域函数以此类型作为返回值:1
fun TextView.buildSpannableString(init: DslSpannableStringBuilder.() -> XxxResult)
在作用域内声明一个必须被执行的函数:
1
2
3
4
5
6interface DslSpannableStringBuilder {
//增加一段文字
fun addText(text: String, method: (DslSpanBuilder.() -> Unit)? = null)
//必须执行的行为
fun mustCall():XxxResult
}如果作用域是接口,要有对应的实现类;使用接口+实现类的方式可以隐藏内部;
理解了上面的这些知识之后,像这样“不好看的代码”,想必你也能理解为什么可以不调用onDispose
了吧:
1 |
|