Skip to content
Maozy's Blog
Go back

[Jetpack Compose] state案例分析

关于状态提升 (State Hoisting) 和单向数据流 (UDF)想再补充个实战场景加深印象

之前这篇文章[Jetpack Compose] AndroidView最后有个AndroidView 的实战案例,正好昨天也整理了 state 的相关内容,就拿代码直接来重新巩固下:

@Composable
fun MyScreen() {
    // 定义一个状态:当前的网址
    var currentUrl by remember { mutableStateOf("https://google.com") }

    Column {
        Button(onClick = { currentUrl = "https://github.com" }) {
            Text("切换到 GitHub")
        }

        AndroidView(
            // factory 只跑一次 new 一个 WebView 出来
            factory = { context -> 
                WebView(context).apply {
                    // 比如在这里配置一些固定不变的设置
                    settings.javaScriptEnabled = true 
                }
            },
            // update 会在 currentUrl 变化时,自动重新执行!
            update = { webView -> 
                // 每次 currentUrl 变了,这里就会拿着原来的 webView,去 load 新的 Url
                webView.loadUrl(currentUrl) 
            }
        )
    }
}

上面代码就是“状态提升 (State Hoisting)”和“单向数据流 (UDF)”的完美体现:

有个经常会提到的底层细节:状态改变时,到底是谁被重新执行了?

一、重组作用域 (Recomposition Scope)

在我之前的理解中:“currentUrl 变了,所以 Compose 框架会直接回调 AndroidViewupdate 方法”。

在底层机制上,并不是直接去调 update 的,而是触发了更大范围的“重组”,然后顺藤摸瓜执行到 update

1.1 谁真正监听了 currentUrl

这里需要纠正一个初次学习 compose 很容易踩的坑:Column 不是真正的父类

在 Compose 中,只有非内联(non-inline)的 @Composable 函数才能形成一个“重组作用域(Recomposition Scope)”。

1.2 状态改变后的“顺藤摸瓜”

Button 被点击,currentUrl 的值发生改变。由于 MyScreen 读取了这个值,Compose 框架会说:

MyScreen 的数据过期了,MyScreen 你给我重新执行一遍!

于是,MyScreen 这个函数开始从头到尾重新跑

  1. 跑到 ButtonCompose 框架会检查传给 Button 的参数变了没有。因为 Button 这里并没有用到 currentUrl 的值(它只传了一个死的回调逻辑),参数没变,Compose 非常聪明地触发智能跳过(Smart Recomposition)Button 内部不重组。

  2. 跑到 AndroidViewCompose 发现,传给 AndroidViewupdate 参数(那个 Lambda 表达式)里面包着最新的 currentUrl。因为参数变了,AndroidView 内部会被触发更新。它会跳过 factory(因为只跑一次),然后重新执行 update 这个 Lambda,把新传入的 url load 进去。

1.3 最佳实践

这种设计的模式,是官方强烈推荐的最佳实践。

以前用 Java 写这段逻辑,大概是在 Button 的点击事件里,直接写一句 webView.loadUrl("...")。这就是命令式编程(直接让 A 去操作 B)。但在 Compose 中,Button 根本不知道 WebView 的存在,这两者是完全解耦的。

Button 只负责改 State,WebView 只负责读 State。

二、总结

在 Compose 中,我们极其推崇将状态提升到父组件中管理:子组件不应该互相持有引用或直接通信,而是通过读取共享的 State 来实现联动。

当 State 改变时,Compose 会触发读取了该 State 的最近的 Recomposition Scope(如父组件)进行重组。

在重组过程中,利用 Compose 编译器的‘智能跳过(Smart Recomposition)’机制,只有参数真正发生变化的子组件(如本文案例中的 AndroidView)才会重新执行更新逻辑,而其他无关组件(如 Button)会被安全跳过。

这种设计既保证了单向数据流的纯粹性,又兼顾了极高的渲染性能。


Share this post on:

Previous Post
[Jetpack Compose] 纯Compose VS 传统Android
Next Post
[Jetpack Compose] AndroidView