UI响应式更新
UI响应式更新
在客户端原生开发中,我们更新UI的步骤通常为:
- 更新数据
- 拿到
View引用 - 将数据设置给
View
而在Kuikly中,我们可省掉这些步骤,只专注于数据的更新,KuiklyCore会负责帮你将数据更新到UI。举个例子:
@Page("HelloWorldPage")
internal class HelloWorldPage : Pager() {
private var counter by observable(0)
override fun body(): ViewBuilder {
val ctx = this
return {
attr {
backgroundColor(Color.WHITE)
justifyContentCenter()
alignItemsCenter()
}
Text {
attr {
text(ctx.counter.toString())
fontSize(20f)
fontWeightBold()
}
}
Button {
attr {
absolutePosition(bottom = 30f, right = 30f)
size(80f, 80f)
borderRadius(10f)
backgroundColor(Color.BLUE)
titleAttr {
text("点击更新")
fontSize(15f)
color(Color.WHITE)
fontWeightBold()
}
}
event {
click {
ctx.counter++
}
}
}
}
}
}

在这个例子中,含有一个居中的Text组件和右下角有一个Button组件。在Text组件中,我们将counter变量的值设置给Text组件, 然后我们监听了Button组件的click事件,并在click事件触发的时候,对counter变量进行累加,每次累加的时候,你会发现Text的文本会自动更新。
响应式字段observable<T>
在上述例子中,为什么Text组件的文本会自动更新呢? 答案是Text组件的text属性绑定了一个响应式字段counter。 counter字段与普通的变量不一样,它通过by observable代理,将字段变成一个响应式字段, 这样当响应式字段被设置到组件的属性后, 每次响应式字段更新时, 依赖该字段的属性都会自动更新, 从而到达UI属性响应式更新的目的。
在Kuikly中,你可以通过by observable<T>将一个字段变成响应式字段,然后绑定到UI组件的属性,这样UI组件的属性就能自动监听数据变化而自动更新, 例如:
private var counter by observable(0)
响应式容器observableList<T>()和observableSet<T>()
在Kuikly中,响应式字段可分为两类
- 单值类型:
observable<T>() - 容器类型:
observableList<T>(),observableSet<T>()
其中observableList和observableSet为响应式容器, 常用于结合vfor语句来用来循环创建列表下的Item, 并且当往响应式容器添加数据时, 列表能够自动新建Item。
举个例子:
@Page("HelloWorldPage")
internal class HelloWorldPage : Pager() {
private var list by observableList<String>()
override fun created() {
super.created()
// mock data
for (i in 0 until 10) {
list.add(i.toString())
}
}
override fun body(): ViewBuilder {
val ctx = this
return {
attr {
backgroundColor(Color.WHITE)
}
List {
attr {
flex(1f)
}
vfor({ ctx.list }) { item ->
View {
attr {
flexDirectionRow()
margin(20f)
}
Text {
attr {
text(item)
fontSize(20f)
fontWeightBold()
}
}
Image {
attr {
marginLeft(20f)
size(50f, 50f)
src("https://vfiles.gtimg.cn/wupload/xy/componenthub/TbyiIqBP.jpeg")
}
}
}
}
}
}
}
}

在上面的例子中,我们先通过by observableList<T>声明了一个响应式容器, 并在List组件下将容器绑定在vfor语句的闭包中, 让List的列表具有自动更新的能力。 例如,一开始我们往数据list容器添加10个数据,因此当开始运行的时候,List组件下会有10个Item。当我们在运行时的某个时刻往数据list中再添加一个数据,此时你会发现List组件会自动创建第11个Item。
常见错误
错误1:在属性设置中未引用响应式字段
如果属性设置中没有直接引用响应式字段,而是将值提前取出存储到普通变量中,则该属性不会响应数据变化。
// 错误示例
private var textContent by observable("Hello")
override fun body(): ViewBuilder {
val ctx = this
val content = ctx.textContent // 错误:提前取值,断开了响应式依赖
return {
Text {
attr {
text(content) // 错误:使用普通变量,不会响应ctx.textContent的变化
}
}
}
}
// 正确示例
private var textContent by observable("Hello")
override fun body(): ViewBuilder {
val ctx = this
return {
Text {
attr {
text(ctx.textContent) // ✅ 直接引用响应式字段
}
}
}
}
错误2:修改拥有响应式字段的类实例而非响应式字段本身
响应式系统只能监听响应式字段本身的赋值操作,如果你修改的是对象的内部属性(或者拥有响应式字段的类实例),UI不会自动更新。
class Obj1(field: String) {
var field: String = field
}
class Obj2(scope: PagerScope, field: String) {
var field by scope.observable(field)
}
internal class DebugPage : BasePager() {
var obj1 = Obj1("")
var obj2 = Obj2(this, "")
var observableObj1 by observable(Obj1(""))
var observableObj2 by observable(Obj2(this, ""))
override fun body(): ViewBuilder {
val ctx = this
return {
Text { attr { text(ctx.obj1.field) } }
Text { attr { text(ctx.obj2.field) } }
Text { attr { text(ctx.observableObj1.field) } }
Text { attr { text(ctx.observableObj2.field) } }
Button {
event {
click {
// ❌错误:field不是响应式字段,不会触发更新
ctx.obj1.field = "change"
// ❌错误:obj1不是响应式字段,不会触发更新
ctx.obj1 = Obj1("change")
// ✅正确:field是响应式字段,可以更新
ctx.obj2.field = "change"
// ❌错误:obj2不是响应式字段,不会触发更新
ctx.obj2 = Obj2(ctx, "change")
// ❌错误:改变响应式元素内部字段,不会触发更新
ctx.observableObj1.field = "change"
// ✅正确:observableObj1是响应式字段,可以更新
ctx.observableObj1 = Obj1("change")
// ⚠️正确:field是响应式字段,可以更新,但observableObj2声明为响应式元素是多余的
ctx.observableObj2.field = "change"
// ⚠️正确:observableObj2是响应式字段,可以更新,但内部字段声明为响应式是多余的
ctx.observableObj2 = Obj2(ctx, "change")
}
}
}
}
}
}
下一步
在这节中,我们学习了如何使用响应式字段和响应式容器来达到UI自动更新的目的。下一步,我们来学习Kuikly中的语句指令