- PVSM.RU - https://www.pvsm.ru -
Мысль перевести фронт на какой-либо js фреймворк появилась одновременно с возможностью писать React на Kotlin. И я решил попробовать. Основная проблема: мало материалов и примеров (постараюсь эту ситуацию поправить). Зато у меня полноценная типизация, безбоязненный рефакторинг, все возможности Kotlin, а главное, общий код для бека на JVM и фронта на Javascript.
В этой статье будем писать страницу на Javasript + React параллельно с её аналогом на Kotlin + React. Чтобы сравнение было честным, я добавил в Javasript типизацию.
[1]
Добавить типизацию в Javascript оказалось не так просто. Если для Kotlin мне понадобились gradle, npm и webpack, то для Javascript мне понадобились npm, webpack, flow и babel с пресетами react, flow, es2015 и stage-2. При этом flow тут как-то сбоку, и запускать его надо отдельно и отдельно дружить его с IDE. Если вынести за скобки сборку и подобное, то для непосредственного написания кода с одной стороны остается Kotlin+React, а с другой Javascript+React+babel+Flow+ES5|ES6|ES7.
Для нашего примера сделаем страничку со списком машин и возможностью фильтрации по марке и цвету. Возможные для фильтрации марку и цвет подтаскиваем с бека один раз при первой загрузке. Выбранные фильтры сохраняем в query. Машины отображаем в табличке. Мой проект не про машины, но общая структура в целом похожа на то, с чем я регулярно работаю.
Результат выглядит вот так (дизайнером мне не быть):
Конфигурацию всей этой шайтан-машины я здесь описывать не буду, это тема для отдельной статьи (пока можно курить исходники от этой).
Для начала надо подгрузить бренды и доступные цвета с бека.
javascript | kotlin |
---|---|
|
|
Выглядит очень похоже. Но есть и различия:
suspend
модификатом, что логично: componentDidMount синхронный метод. Зато можно в любом месте кода вставить асинхронный блок launch { }
. Можно в явном виде принимать асинхронную функцию в параметре или поле класса (чуть ниже пример из моего проекта). suspend fun parallel(vararg tasks: suspend () -> Unit) {
tasks.map {
async { it.invoke() } //запускаем каждый task, но не ждем ответа. async {} возвращает что-то вроде promise
}.forEach { it.await() } //все запустили, теперь ждем
}
override fun componentDidMount() {
launch {
updateState {
parallel({
halls = hallExchanger.all()
}, {
instructors = instructorExchanger.active()
}, {
groups = fetchGroups()
})
}
}
}
Теперь подгрузим машины с бека используя фильтры из query
JS:
async loadCars() {
let url = `/api/cars?brand=${this.state.brand || ""}&color=${this.state.color || ""}`;
this.setState({
cars: await (await fetch(url)).json(),
loaded: true
});
}
Kotlin:
private suspend fun loadCars() {
val url = "/api/cars?brand=${state.brand.orEmpty()}&color=${state.color.orEmpty()}"
updateState {
cars = fetchJson(url, Car::class.serializer().list) //(*)
loaded = true
}
}
Хочу обратить внимание на Car::class.serializer().list
. Дело в том, что jetBrains написала библиотеку для сериализации/десериализации, которая одинаково работает на JVM и JS. Во-первых, меньше проблем и кода в случае если бек на JVM. Во-вторых валидность пришедшего json проверяется во время десериализации, а не когда-нибудь при обращении, так что при смене версии бека, и при интеграциях впринципе, проблемы будут находиться быстрее.
Напишем stateless component для отображения двух выпадающих списков. В случае Kotlin это будет просто функция, в случае js — отдельный компонент, который будет генерироваться react loader при сборке.
javascript | kotlin |
---|---|
|
|
Первое, что бросается в глаза — HomeHeaderProps в JS части, мы вынуждены объявить входящие параметры отдельно. Неудобно.
Ещё немного изменился синтаксис Dropdown. Я тут использую primereact [5], естественно, пришлось писать kotlin обертку. С одной стороны это лишняя работа (слава богу, есть ts2kt [6]), но с другой — это возможность местами сделать api удобнее.
Ну и немного синтаксического сахара при формировании итемов для dropdown. })))}
в js варианте выглядит интересно, но это не беда. Зато выпрямление последовательности слов намного приятнее: «преобразуем цвета в items и добавляем `all` по-умолчанию», вместо «добавляем `all` к цеветам преобразованным в items». Это кажется небольшим бонусом, но когда у тебя несколько таких переворотов подряд…
Теперь нужно при выборе фильтров по марке и цвету изменять state, подгружать машины с бека и менять урл.
javascript | kotlin |
---|---|
|
|
И здесь опять проблема с дефолтными значениями параметров. Почему-то flow не разрешил мне одновременно иметь типизацию, деструктор и дефолтное значение взятое из state. Возможно, просто бага. Но, если бы все-таки вышло, то пришлось бы объявить тип за пределами класса, т.е. вообще на экран выше или ниже.
Последнее что нам осталось сделать — написать stateless component для отрисовки таблицы с машинами.
javascript | kotlin |
---|---|
|
|
Здесь видно, как я выпрямил api primefaces, и как в kotlin-react задавать стиль. Это обычный json, как и в js варианте. В своем проекте я делал обертку, которая выглядит также, но со строгой типизацией, насколько это возможно в случае html стилей.
Ввязываться в новую технологию рискованно. Мало гайдов, на stack overflow ничего нет, не хватает некоторых базовых вещей. Но в случае с Kotlin мои затраты окупились.
Пока я готовил эту статью, я узнал кучу новых вещей о современном Javascript: flow, babel, async/await, шаблоны jsx. Интересно, насколько быстро эти знания устареют? И всё это не нужно, если использовать Kotlin. При этом знать о React нужно совсем немного, потому что большая часть проблем легко решается при помощи языка.
А что Вы думаете о замене всего этого зоопарка одним языком с большим набором плюшек впридачу?
Для заинтересовавшихся исходники [7].
P.S.: В планах написать статьи об конфигах, интеграции с JVM и о dsl формирующем одновременно react-dom и обычный html.
Уже написаные статьи о Kotlin:
Послевкусие от Kotlin, часть 1 [8]
Послевкусие от Kotlin, часть 2 [9]
Послевкусие от Kotlin, часть 3. Корутины — делим процессорное время [10]
Автор: Нефедьев Георгий
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/290364
Ссылки в тексте:
[1] Image: https://habr.com/post/418553/
[2] lateinit: https://kotlinlang.org/docs/reference/properties.html#late-initialized-properties-and-variables
[3] корутины: https://kotlinlang.ru/docs/reference/coroutines.html
[4] yield: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-sequence-builder/yield.html
[5] primereact: https://www.primefaces.org/primereact/
[6] ts2kt: https://github.com/Kotlin/ts2kt
[7] исходники: https://github.com/gnefedev/kotlin-js-react
[8] Послевкусие от Kotlin, часть 1: https://habrahabr.ru/post/331280/
[9] Послевкусие от Kotlin, часть 2: https://habrahabr.ru/post/337002/
[10] Послевкусие от Kotlin, часть 3. Корутины — делим процессорное время: https://habrahabr.ru/post/339618/
[11] Источник: https://habr.com/post/418553/?utm_source=habrahabr&utm_medium=rss&utm_campaign=418553
Нажмите здесь для печати.