我在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?
答案 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
,但您需要禁止警告: - )