ID

ID可以是任意类型。子组件的ID会继承父组件的ID并延长。

如何使用ID

向函数传入ID

最保险的方式是向每个ui函数都手动传入一个不同的ID(只需保证在同一个scope内不同)。

Row(id = Unit) {
    if(bool) Button(id = 1) {}
    listOf("a","b","c").forEach {
        TextFlatten(id = it) { it.emit() }
    }
    Column {
        Button(id = "a") {}
        Spacer(id = "b")
        Spacer(id = "c")
    }
    Column {}
}

但是维护这么多id是很繁琐的。你会发现代码写成下面这样,控件依然会获得不同的id:

Row {
    if(bool) Button {}
    listOf("a","b","c").forEachWithId {
        TextFlatten { it.emit() }
    }
    Column {
        Button {}
        Spacer {}
        Spacer {}
    }
    Column {}
}

为什么能简化

kotlinJVM上的实现决定。以上面代码为例:每次执行到Column{...}时,传入的lambda可以用lambda::class==来判断是否在同一位置声明;对于{},可以用==来判断它们是否在同一位置声明。以区别两个Column

我没找到任何kotlin官方关于这个行为的保证,所以这是一个比较激进的缩简。

所以,at your own risk。我倾向更简单的写法。

Iterable<T>.forEachWithId在每个循环,会在ID后面加上被迭代的元素。

传递ID

context(ctx: DslContext)
fun MyFunc(color:Color,id:Any) = Row(id = id) {
    // ...    
}

context(ctx: DslContext)
fun MyFunc(
    color:Color,
    id:Any? = null,
    lambda: context(DslPreventContext) ()->Unit
) = Box(id = id ?: lambda::class) {
    // ...    
}

对于有lambda参数的函数,把lambda::class作为默认ID,同时允许调用者传入自定义ID。null代表使用默认ID。最好不要给lambda参数设默认值。

对于没有lambda参数的函数,把id:Any放在最后,以便使用MyFunc {}传入{}作为ID。

为什么需要ID

ID用来区分不同的组件。关于为什么需要ID,看下面这个示例:

context(ctx: DslContext)
fun MyUIComponent(text:String) = run {
    var counter by remember(0)
    Button { TextFlatten { "$text$counter".emit() } }.clickable { counter++ }
}

// in a ui function
Row {
    if(showCounter1) MyUIComponent("counter1")
    MyUIComponent("counter2")
}

两个counter会变得无法区分。在读写counter时,kotlin会调用remember返回的对象的getValue(thisRef:Any?, property: KProperty<*>)setValue(thisRef: Any?,property: KProperty<*>,value:T)。 每一次在代码中声明by委托属性,对应一个KProperty对象。通过比较KProperty,能区分这个属性在源码中的位置是否相同,但是没法区分同一个函数多次调用。


This site uses Just the Docs, a documentation theme for Jekyll.