我如何在Kotlin中为属性的后台字段设置JsName?

我在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字段名称变成类似于_id_mmz446$_0 ,所以PouchDB拒绝目的。 如果我将@JsName("_id")到属性,只影响生成的getter和setter – 它仍然留下一个损坏的名称的支持字段。

而且,对于名称不以_开头的虚拟属性,PouchDB将接受该对象,但它只存储带有错误名称的支持字段,而不是存储名称恰当的属性。

现在我可以通过使它们不是虚拟的来解决问题。 但是我正在考虑在Kotlin中分享PouchDoc和非PouchDoc类之间的接口,而且我似乎无法做到这一点。

任何想法我可以做这个工作,还是需要一个Kotlin语言的变化?

我认为你的问题应该覆盖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()) } 

我从JetBrains的一个人Alexey Andreev那里得到了一个很好的答案,在JetBrains论坛上: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调用jsobject[property.name] = value的set函数,该函数再次调用委托…

(另外,事实证明,即使您可以定义一个像委托人一样工作的getter / setter对,就可以像在@ bashor的PouchDoc2示例中显示的PouchDoc2 ,将一个委托放在一个属性中。

使用外部类

Alexey在Kotlin论坛上的回答基本上是这样的:“你将业务(包括行为)和持久性(仅数据)层混合在一起:正确的答案是明确地将序列化到JS,但我们还没有提供;一个解决方法,使用外部类“。 我认为,关键是外部类不会变成定义属性获取器/设置器的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("{}") 

但对我来说(与科特林1.1)变成

 function jsobject() { } 

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

其次,你不能这样做

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

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

最后,你可以标记createinline ,但你需要压制一个警告:-)