2、Compose核心概念:声明式UI思想、Composable函数、重组(Recomposition)、状态与事件、单向数据流

好,咱们正式开始啃 Compose 的核心概念。

说实话,我刚从传统 View 体系切换到 Compose 时,最大的感受就是——脑子得换个操作系统。以前写 XML 布局,再在 Activity 里 findViewById,然后手动更新 UI。这套流程我用了快十年,肌肉记忆都形成了。但 Compose 不一样,它逼着你用全新的方式思考 UI 的构建。

这一节,我会把五个最核心的概念串起来讲。你理解了它们,就相当于拿到了 Compose 世界的钥匙。

2.1 声明式UI思想:你告诉它“是什么”,而不是“怎么做”

传统 View 系统是命令式的。你得像下棋一样,一步步告诉系统:

  • “找到这个 TextView”
  • “把它的文字改成‘你好’”
  • “再把这个按钮变灰”

代码里全是 findViewByIdsetTextsetEnabled 这种操作。UI 状态一多,代码就乱成一锅粥。

Compose 用的是声明式思想。你只需要描述 UI 在当前状态下应该长什么样。状态变了,UI 自动跟着变。

举个例子。以前写一个点赞按钮:

// 命令式
if (isLiked) {
    likeButton.setImageResource(R.drawable.ic_liked)
    likeButton.setColorFilter(Color.RED)
} else {
    likeButton.setImageResource(R.drawable.ic_unliked)
    likeButton.setColorFilter(Color.GRAY)
}

在 Compose 里,你直接写:

// 声明式
@Composable
fun LikeButton(isLiked: Boolean) {
    Image(
        painter = if (isLiked) painterResource(R.drawable.ic_liked) 
                  else painterResource(R.drawable.ic_unliked),
        contentDescription = null,
        colorFilter = if (isLiked) ColorFilter.tint(Color.Red) 
                      else ColorFilter.tint(Color.Gray)
    )
}

看到了吗?没有 if-else 的赋值操作。UI 完全由数据驱动。我个人觉得,这种思维转变是学习 Compose 最大的门槛。一旦跨过去,后面就顺了。

核心理解:声明式 UI = 状态 + 函数。给定相同的状态,永远得到相同的 UI。

2.2 Composable函数:UI 的基本积木块

在 Compose 里,一切 UI 都是函数。这个函数用 @Composable 注解标记,我们叫它可组合函数

它有几个特点:

  • 无返回值:它不返回 View 对象,而是直接发射 UI 节点到组合树里
  • 可调用其他 Composable 函数:就像搭积木,小的拼成大的
  • 执行顺序不保证:别依赖代码行号来推断 UI 顺序

一个最简单的例子:

@Composable
fun Greeting(name: String) {
    Text(text = "你好,$name!")
}

@Composable
fun MyScreen() {
    Column {
        Greeting("小明")
        Greeting("小红")
    }
}

嗯,这里要注意:Composable 函数不能直接写在普通函数里。它只能在另一个 Composable 函数的作用域内被调用。这是 Compose 编译器插件的硬性要求。

我的习惯:文件名和 Composable 函数名保持一致。比如 Greeting.kt 里放 Greeting() 函数。这样找代码时不用动脑子。

2.3 重组(Recomposition):UI 自动更新的秘密

重组是 Compose 最神奇的地方。当状态发生变化时,Compose 不会重新执行整个 UI 树,而是只重新执行那些依赖该状态的 Composable 函数

我举个例子你就明白了:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text("当前计数:$count")
        Button(onClick = { count++ }) {
            Text("点击加一")
        }
    }
}

当你点击按钮时,count 变了。Compose 会检测到 Text("当前计数:$count") 依赖了 count,于是只重组这个 Text。Button 和 Column 都不会重新执行。

为什么会这样?因为 Compose 在编译期就分析出了每个 Composable 函数读取了哪些状态。这叫跳过(Skipping)优化。

我曾经踩过的坑:在 Composable 函数里写了耗时操作,比如网络请求或文件读写。重组发生时,这些操作会重复执行!记住:Composable 函数应该只做 UI 描述,副作用交给 LaunchedEffect 或协程

2.4 状态与事件:UI 的输入和输出

在 Compose 的世界里,状态是向下传递的,事件是向上传递的。这是单向数据流的核心。

状态就是 UI 的数据。比如:

  • 输入框里的文字
  • 开关是否打开
  • 列表的数据源

事件就是用户的操作。比如:

  • 点击按钮
  • 滑动列表
  • 输入文字

看一个完整的例子:

@Composable
fun LoginScreen() {
    var username by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }

    Column(modifier = Modifier.padding(16.dp)) {
        // 状态向下传递
        TextField(
            value = username,
            onValueChange = { username = it },  // 事件向上传递
            label = { Text("用户名") }
        )
        TextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("密码") }
        )
        Button(onClick = { /* 登录逻辑 */ }) {
            Text("登录")
        }
    }
}

你想想看,username 这个状态在顶层定义,然后传给 TextField。TextField 不直接修改状态,而是通过 onValueChange 回调把新值传回来。这就是状态提升

记住这个原则:谁拥有状态,谁就负责修改状态。子组件只负责展示和通知。

2.5 单向数据流:让数据变化可预测

单向数据流说白了就是:数据只能从父流向子,不能反过来

如果子组件想改变数据,它得通过回调通知父组件,由父组件来修改。这样数据的变化路径是清晰的,调试起来特别方便。

我画了一张图,帮你理解这个流程:

单向数据流示意图 父组件(持有状态) 状态:count = 5 子组件(展示+通知) 显示:count = 5 状态向下传递 事件向上传递 数据永远从父流向子,子通过回调通知父修改状态

你看,箭头方向很清晰。父组件持有状态,传给子组件展示。子组件不能直接改状态,只能通过回调说“用户点了按钮”。父组件收到回调后,自己修改状态,然后触发重组。

这样做的好处是什么?

  • 可预测:数据变化路径唯一,不会出现“这个值到底是谁改的”这种问题
  • 可测试:给相同的状态,UI 输出是确定的
  • 可调试:出问题时,顺着数据流往上查就行
避坑指南:我曾经在项目里为了让代码少写几行,直接把状态定义在子组件里,然后通过参数传回父组件。结果 UI 状态和业务逻辑混在一起,改一个需求要动三个文件。后来老老实实做状态提升,代码反而更清爽了。

2.6 把这些概念串起来

咱们用一张表格总结一下这五个概念的关系:

概念 一句话理解 我的经验
声明式UI 描述“是什么”,不是“怎么做” 思维转换是最大门槛
Composable函数 UI 的基本积木块 文件名和函数名保持一致
重组 状态变了,只更新依赖它的部分 别在 Composable 里写副作用
状态与事件 状态向下,事件向上 状态提升让代码更干净
单向数据流 数据只能从父流向子 调试时你会感谢这个设计

说白了,Compose 的核心就是用函数描述 UI,用状态驱动更新,用事件处理交互。这三个点抓住了,后面学什么组件、什么布局都不怕。

嗯,这一节的内容就到这。你先把这几个概念消化一下,尤其是“状态提升”和“重组”这两个,后面几乎每节课都会用到。


公众号:蓝海资料掘金营,微信deep3321