| 武器 | 用法 | 典型场景 |
|---|---|---|
observable<T> |
var x by observable(0) |
计数器、loading、表单字段、单值 |
observableListOf<T>() |
val items = observableListOf<Item>() |
列表、Todo、消息流、瀑布流 |
observableMapOf<K,V>() |
val cache = observableMapOf<String, User>() |
缓存、字典、动态字段集 |
"声明 = 实现" —— 你只描述"UI 等于什么状态的什么样子",不操作 UI 本身。状态变化 → UI 自动更新。
// ❌ 命令式:手动同步 UI(容易漏更新)
btn.onClick = {
count++
text.text = "Count: $count" // ← 你得记住每次都写
}
// ✅ 响应式:声明依赖(永远不会漏)
private var count by observable(0)
Text { attr { text("Count: $count") } }
View { event { click { count++ } } }
currentBlock = 当前 attr。每次读 observable 字段,
getter 把 currentBlock 注册为该字段的"订阅者"。
class ObservableDelegate<T>(initial: T) : ReadWriteProperty<Any?, T> {
private var value = initial
private val subscribers = mutableListOf<() -> Unit>()
override fun getValue(thisRef, prop): T {
// ★ STEP 1:依赖收集
currentBlock?.let { subscribers.add(it) }
return value
}
override fun setValue(thisRef, prop, newValue: T) {
if (value != newValue) {
value = newValue
// ★ STEP 2 + 3:通知 + 加入待重跑队列
subscribers.forEach { schedulerQueue.add(it) }
}
}
}
| 框架 | 语法 | 追踪粒度 |
|---|---|---|
| Kuikly | var x by observable(0) | attr 块 |
| Vue 3 | const x = ref(0) | render effect |
| Compose | var x by mutableStateOf(0) | @Composable 函数 |
| SolidJS | const [x, setX] = createSignal(0) | JSX 表达式 |
底层都是观察者模式 + 依赖追踪,差别在于追踪粒度和上下文管理方式。
右边面板的「读取依赖」是用蓝色高亮的字段。改任何一个 → 依赖它的 UI 自动刷新(高亮闪一下)。
data class TodoItem(val id: Int, val title: String, val done: Boolean)
private val todos = observableListOf<TodoItem>()
private var input by observable("")
private var filter by observable("all")
private val filteredTodos: List<TodoItem> get() = when (filter) {
"active" -> todos.filter { !it.done }
"done" -> todos.filter { it.done }
else -> todos
}
private val activeCount: Int get() = todos.count { !it.done }
// 切换完成状态:copy() 整个 item
fun toggle(todo: TodoItem) {
val idx = todos.indexOfFirst { it.id == todo.id }
if (idx >= 0) todos[idx] = todo.copy(done = !todo.done)
}
// ❌ 错误
private var count = 0 // 普通字段,改了 UI 不更新
// ✅ 正确
private var count by observable(0)
// ❌ 错误
private val items = mutableListOf<Item>()
items.add(...) // UI 不刷新
// ✅ 正确
private val items = observableListOf<Item>()
items.add(...)
// ❌ 错误
val current = ctx.count // 读取发生在 attr 闭包外
return {
Text { attr { text("$current") } } // current 不再变
}
// ✅ 正确
return {
Text { attr { text("${ctx.count}") } }
}
// ❌ 错误:只算一次
private var fullName = "$firstName $lastName"
// ✅ 正确:每次访问重新计算
private val fullName: String get() = "$firstName $lastName"
// ❌ 反例
object Global {
var anyTempState by observable(...) // 全局响应式管理混乱
}
// ✅ 正确:长期数据用 Store,短期数据放 Pager
object UserStore {
var loggedIn by observable(false) // 长期、跨页面用
}
class HomePage : Pager() {
private var loading by observable(false) // 短期、本页用
}
| 写法 | 能驱动 UI? |
|---|---|
var x by observable(0); x = 1 | ✅ |
val list = observableListOf(); list.add(...) | ✅ |
list[0] = newItem (data class) | ✅ |
list[0].field = ... (字段非 observable) | ❌ |
list[0].field = ... (字段是 observable) | ✅ |
val x = ctx.count (在 attr 外读) | ❌ |
val fullName: String get() = ... | ✅ |
val fullName = ... (字段初始化) | ❌ |