#reactjs #kotlin #generics #kotlin-js
#reactjs #kotlin #общие #kotlin-js
Вопрос:
Абстрактная проблема:
Рассмотрим следующее:
data class Data(val i: Int, val s: String = "")
fun <A1, A2, D> make(ctor: KFunction2<A1, A2, D>, sideEffect: (D) -> D) =
{ a1: A1, a2: A2 -> sideEffect(ctor(a1, a2)) }
val makeData = make(::Data) {
it.also { println("Side effect: i=${it.i}, s=${it.s}") }
}
Я могу вызвать makeData со всеми аргументами конструктора, чтобы получить
экземпляр Data
, а также вызвать некоторый побочный эффект. Однако я
не могу опустить параметры и вместо этого использовать значения конструктора по умолчанию:
val d1 = makeData(42, "hi")
val d2 = makeData2(42) // Error: No value passed for parameter 'p2'
Как я могу переписать generic make
таким образом, чтобы его вывод был функцией со значениями по умолчанию?
Чего я пытаюсь достичь:
С помощью Kotlin React я объявляю свойства следующим образом:
external interface VideoListProps: RProps { var videos: List<Video> }
Чтобы использовать компонент в render(), я могу написать:
child(VideoList::class) { attrs.videos = unwatchedVideos }
Мне это не нравится по трем причинам: (a) использование компонента намного более подробное, чем по сравнению с TypeScript / TSX,
(b) свойства должны быть объявлены как var, в то время как они должны быть доступны только для чтения (val), (c) нет принудительного выполнения того, что член фактически объявлен; props.videos
может закончиться как null
во время выполнения, даже если интерфейс Props не объявляет его как обнуляемый.
В руководстве по Kotlin React предлагается использовать лямбды с приемниками, например:
fun RBuilder.videoList(handler: VideoListProps.() -> Unit): ReactElement {
return child(VideoList::class) {
this.attrs(handler)
}
}
Теперь я могу использовать компонент следующим образом:
videoList { videos = unwatchedVideos }
Хотя вызывающий сайт теперь выглядит намного более лаконичным, теперь мне приходится копировать / вставлять и адаптировать фрагмент лямбда-кода для каждого компонента, который я пишу. Это намного более подробно, чем в TypeScript, где мне не нужна эта конструкция. Более того, лямбда-файл videoList не имеет доступа к другим элементам RBuilder, поскольку this
теперь указывает на RProps . Поэтому, если мне нужно определить реакцию key
, я в растерянности и должен использовать синтаксис child(…) из предыдущего. И, наконец, проблема (c) сверху все еще существует.
Чтобы сделать это лучше, я написал следующие вспомогательные конструкции:
fun <P : RProps, A1> RBuilder.childWithProps(
klass: KClass<out Component<P, *>>,
ctor: KFunction1<A1, P>
) =
{ a1: A1, handler: RHandler<P> -> child(klass.rClass, ctor(a1), handler) }
fun <P : RProps, A1, A2> RBuilder.childWithProps(
klass: KClass<out Component<P, *>>,
ctor: KFunction2<A1, A2, P>
) =
{ a1: A1, a2: A2, handler: RHandler<P> -> child(klass.rClass, ctor(a1, a2), handler) }
// more for KFunction3, KFunction4, ... up to a reasonable amount of members, e.g. 16
Я признаю, что это тоже некрасиво, но мне нужно написать это только один раз. Теперь я могу объявлять такие реквизиты (обратите внимание на значение val вместо var):
data class VideoListProps(val videos: List<Videos>) : RProps
Дополнительный код для каждого компонента теперь намного более лаконичен:
val RBuilder.videoList get() = childWithProps(VideoList::class, ::VideoListProps)
И используйте ее следующим образом:
videoList(watchedVideos) { /* you can set React key or children here if needed */ }
Kotlin теперь помечает это как ошибку, если я забуду установить все свойства.
Теперь все выглядит лаконично, я пишу даже немного меньше кода, чем в TypeScript / TSX. Единственная оставшаяся проблема заключается в том, что теперь мне всегда приходится передавать все элементы, поскольку любые значения по умолчанию конструктора данных все равно будут сохраняться в оболочке. Это актуальный вопрос.
Комментарии:
1. Почему бы вам просто не использовать constucted в
D
качестве входных данных?2. Потому что я хотел бы максимально упростить ее использование.
sideEffect(Data1(a, b))
сложнее, чемmakeData1(a, b)
.3. Я переписал вопрос, чтобы прояснить, чего я пытаюсь достичь.