如何在Kotlin中为属性的支持字段设置JsName?

时间:2017-04-29 13:17:38

标签: kotlin kotlin-js-interop

我在1.0.x中使用了Kotlin不支持的JavaScript后端,现在正尝试将我的玩具项目迁移到1.1.x.它是与PouchDB连接的单页Web应用程序的最基本骨骼。要向PouchDB添加数据,您需要具有特定属性_id_rev的JavaScript对象。他们还需要拥有以_开头的任何其他属性,因为它们已被PouchDB保留。

现在,如果我创建一个这样的类,我可以将实例发送到PouchDB。

class PouchDoc(
        var _id: String
) {
    var _rev: String? = null
}

但是,如果我做任何事情来使属性成为虚拟 - 让它们覆盖接口,或者让类打开并创建一个覆盖它们的子类 - _id字段名称会变成类似{ {1}}所以PouchDB拒绝该对象。如果我将_id_mmz446$_0应用于属性,那只会影响生成的getter和setter - 它仍会使支持字段留下错误的名称。

此外,对于名称不以@JsName("_id")开头的任何虚拟属性,PouchDB将接受该对象,但它只存储带有损坏名称的支持字段,而不是名称很好的属性。

现在我可以通过让它们不虚拟来解决问题,我想。但我想在Kotlin中分享PouchDoc和非PouchDoc类之间的接口,似乎我无法做到。

我知道如何才能使这项工作成功,还是需要更换Kotlin?

2 个答案:

答案 0 :(得分:2)

我认为https://youtrack.jetbrains.com/issue/KT-8127

应涵盖您的问题

另外,我还创建了一些其他相关问题: https://youtrack.jetbrains.com/issue/KT-17682 https://youtrack.jetbrains.com/issue/KT-17683

现在你可以使用下一个解决方案之一,IMO第三个是最轻量级的。

interface PouchDoc1 {
    var id: String
    var _id: String
        get() = id
        set(v) { id = v}

    var rev: String?
    var _rev: String?
        get() = rev
        set(v) { rev = v}
}

class Impl1 : PouchDoc1 {
    override var id = "id0"
    override var rev: String? = "rev0"
}

interface PouchDoc2 {
    var id: String 
        get() = this.asDynamic()["_id"]
        set(v) { this.asDynamic()["_id"] = v}

    var rev: String?
        get() = this.asDynamic()["_rev"]
        set(v) { this.asDynamic()["_rev"] = v}
}

class Impl2 : PouchDoc2 {
    init {
        id = "id1"
        rev = "rev1"
    }
}

external interface PouchDoc3 { // marker interface 
}

var PouchDoc3.id: String 
    get() = this.asDynamic()["_id"]
    set(v) { this.asDynamic()["_id"] = v}

var PouchDoc3.rev: String?
    get() = this.asDynamic()["_rev"]
    set(v) { this.asDynamic()["_rev"] = v}

class Impl3 : PouchDoc3 {
    init {
        id = "id1"
        rev = "rev1"
    }
}

fun keys(a: Any) = js("Object").getOwnPropertyNames(a)

fun printKeys(a: Any) {
    println(a::class.simpleName)
    println(" instance keys: " + keys(a).toString())
    println("__proto__ keys: " + keys(a.asDynamic().__proto__).toString())
    println()
}

fun main(args: Array<String>) {
    printKeys(Impl1())
    printKeys(Impl2())
    printKeys(Impl3())
}

答案 1 :(得分:0)

我得到了JetBrains的一个人Alexey Andreev在https://discuss.kotlinlang.org/t/controlling-the-jsname-of-fields-for-pouchdb-interop/2531/的JetBrains论坛上得到了一个很好的答案。在我描述之前,我会提到进一步尝试改进@ bashor的答案。

财产代表

我认为@bashor的答案是急于使用属性委托,但我无法在没有无限递归的情况下工作。

class JSMapDelegate<T>(
        val jsobject: dynamic
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return jsobject[property.name]
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        jsobject[property.name] = value
    }
}

external interface PouchDoc4 {
    var _id: String
    var _rev: String
}

class Impl4() : PouchDoc4 {
    override var _id: String by JSMapDelegate<String>(this)
    override var _rev: String by JSMapDelegate<String>(this)

    constructor(_id: String) : this() {
        this._id = _id
    }
}

委托给jsobject[property.name] = value的调用调用属性的set函数,该函数再次调用委托...

(另外,事实证明你不能在一个接口的属性上放置一个委托,即使你可以定义一个像委托一样工作的getter / setter对,就像@bashor的PouchDoc2示例所示。)

使用外部课程

Alexey在Kotlin论坛上的回答基本上说,“你正在混合业务(与行为)和持久性(仅限数据)层:正确的答案是明确地序列化到JS /从JS,但我们不提供然而,作为一种解决方法,使用外部类。“我认为,关键是外部类不会变成定义属性getter / setter的JavaScript,因为Kotlin不允许您定义外部类的行为。鉴于这个转向,我得到了以下工作,这就是我想要的。

external interface PouchDoc5 {
    var _id: String
    var _rev: String
}

external class Impl5 : PouchDoc5 {
    override var _id: String
    override var _rev: String
}

fun <T> create(): T = js("{ return {}; }")
fun Impl5(_id: String): Impl5 {
    return create<Impl5>().apply {
        this._id = _id
    }
}

keys的输出为

null
 instance keys: _id
__proto__ keys: toSource,toString,toLocaleString,valueOf,watch,unwatch,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__,__proto__,constructor

创建外部类

关于创建外部类实例的三个注释。首先,阿列克谢说要写

fun <T> create(): T = js("{}")

但对我来说(使用Kotlin 1.1)会变成

function jsobject() {
}

,其返回值为undefined。我认为这可能是一个错误,因为官方文档也建议使用更短的形式。

其次,你不能这样做

fun Impl5(_id: String): Impl5 {
    return (js("{}") as Impl5).apply {
        this._id = _id
    }
}

因为它明确地插入了Impl5的类型检查,它会抛出ReferenceError: Impl5 is not defined(至少在Firefox中)。通用函数方法跳过类型检查。我猜这不是一个错误,因为阿列克谢推荐它,但它似乎很奇怪,所以我会问他。

最后,您可以将create标记为inline,但您需要禁止警告: - )