6

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

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

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

但是,如果我做任何事情来使属性虚拟化——让它们覆盖一个接口,或者让类打开并创建一个覆盖它们的子类——_id字段名称就会变成类似的东西_id_mmz446$_0,因此 PouchDB 拒绝该对象。如果我应用@JsName("_id")到该属性,那只会影响生成的 getter 和 setter——它仍然会留下带有损坏名称的支持字段。

此外,对于名称不以 开头的任何虚拟属性_,PouchDB 将接受该对象,但它只存储带有它们的错位名称的支持字段,而不是命名良好的属性。

我想现在我可以通过让它们不是虚拟的来解决问题。但我正在考虑在 Kotlin 中共享 PouchDoc 和非 PouchDoc 类之间的接口,看来我做不到。

知道我该如何完成这项工作,还是需要更改 Kotlin 语言?

4

2 回答 2

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())
}
于 2017-05-02T11:07:55.007 回答
0

我在 JetBrains 论坛上的一位 JetBrains 人员 Alexey Andreev 那里得到了很好的回答,网址为https://discuss.kotlinlang.org/t/controlling-the-jsname-of-fields-for-pouchdb-interop/2531 /。在我描述之前,我将提到进一步失败的改进@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

创建外部类

关于创建外部类实例的三个注意事项。首先,Alexey 说要写

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 中)。通用函数方法跳过类型检查。我猜这不是错误,因为 Alexey 推荐了它,但它看起来很奇怪,所以我会问他。

最后,您可以标记createinline,尽管您需要禁止显示警告:-)

于 2017-05-07T09:09:30.677 回答