Core Mechanism
attr {} When reactive fields are read within a closure, the framework automatically collects dependencies; when the field value changes, the bound UI properties are automatically updated.Only the data needs to be updated, and the UI responds automatically.
Note:
observableinternally uses==to compare the old and new values,and does not trigger an update when they are equal。
Reactive Field Declaration
In Pager(which implements the PagerScope interface) or any PagerScope subclass, use:
// 推荐:PagerScope 扩展函数
import com.tencent.kuikly.core.reactive.handler.observable
import com.tencent.kuikly.core.reactive.handler.observableList
import com.tencent.kuikly.core.reactive.handler.observableSet
var counter by observable(0) // single value var list by observableList() // reactive list, used with vfor var tags by observableSet() // reactive set
### 绑定到 UI
```kotlin
var title by observable("Hello")
Text {
attr {
text(ctx.title) // 直接引用响应式字段,自动绑定
}
}
// 更新 ctx.title = "World" 后,Text 自动刷新
Directive System
Loop Directives: vfor / vforIndex
var list by observableList<String>()
// vfor:遍历 observableList,增删时自动增量更新
List {
attr { flex(1f) }
vfor({ ctx.list }) { item ->
Text { attr { text(item) } }
}
}
// vforIndex:额外提供 index 和 count
vforIndex({ ctx.list }) { item, index, count ->
View {
attr { backgroundColor(if (index % 2 == 0) Color.GRAY else Color.TRANSPARENT) }
Text { attr { text("$index: $item") } }
}
}
A closure must be passed
{ ctx.list }, not a value passed directlyctx.list。
Loop Directive: vforLazy (very large lists)
Only used in ListView (excluding PageList, WaterfallList), dynamically adding and removing virtual nodes outside the visible range:
List {
attr { flex(1f) }
vforLazy({ ctx.list }, maxLoadItem = 50) { item, index, count ->
Text { attr { text(item) } }
}
}
| Parameter | Type | Default Value | Description |
|---|---|---|---|
| itemList | () -> ObservableList<T> |
— | Data source closure |
| maxLoadItem | Int |
30 | Maximum number of virtual nodes in memory; recommended to be 2~3 times the number visible on one screen |
| itemCreator | (T, Int, Int) -> Unit |
— | Build closure |
Conditional Directives: vif / velseif / velse
var state by observable(0)
View {
vif({ ctx.state == 1 }) {
Text { attr { text("状态1") } }
}
velseif({ ctx.state == 2 }) {
View { attr { size(100f, 100f); backgroundColor(Color.GREEN) } }
}
velse {
Text { attr { text("其他状态") } }
}
}
velseif/velsemust immediately followviforvelseif, and no other components can be inserted in between.
Binding Directive: vbind
Similar to a when statement; when the bound value changes,remove all child components and rebuild:
var state by observable(0)
View {
vbind({ ctx.state }) {
when (ctx.state) {
1 -> Text { attr { text("状态1") } }
2 -> View { attr { size(100f, 100f); backgroundColor(Color.GREEN) } }
}
}
}
vbindWhen the bound value isnull, the creator closure is not executed.
Efficient List Updates: diffUpdate
// 基本用法(基于 Myers diff 算法,只增删变化的 Item)
list.diffUpdate(newData)
// 自定义比较(通过 id 判断是否同一元素)
userList.diffUpdate(newUsers) { old, new -> old.id == new.id }
| Method | Behavior | Performance |
|---|---|---|
clear() + addAll() |
Destroy all Items → rebuild | Poor |
diffUpdate(newList) |
Only add and remove changed Items | Excellent |
Common Errors and Constraint Rules
See the following reference files for details:
- Common Errors:COMMON_MISTAKES.md — 8 typical errors such as broken reactive dependencies, modifying an object's internal properties not triggering updates, vfor passing a value instead of a closure, ViewBuilder not being invoked, etc.
- Directive Constraint Rules:DIRECTIVE_RULES.md — the vfor closure must generate exactly one root node, the root node cannot be a directive node, vforLazy is limited to ListView, velseif/velse must immediately follow vif, the index/count update rules of vforIndex, etc.