-
Notifications
You must be signed in to change notification settings - Fork 5
Home
ЧЕРНОВИК БУДУЩЕЙ ВЕРСИИ. ПЕРЕВОД НА АНГЛИЙСКИЙ ПОСЛЕ ВЫЧИТКИ
Reactive Kittens, это набор библиотек, позволяющий разрабатывать современные одностраничные веб-приложения (SPA) на языка Scala с применением функционального реактивного программирования (FRP). Фокус набора -- мобильные приложения, основанные на вэб-технологиях. Основной особенностью набора является возможность разделить потоки выполнения для рендеринга DOM и для выполнения логики приложения. Это позволяет сделать приложения более отзывчивыми, избавиться от "залипания" интерфейса и пользоваться ресурсами нескольких ядер CPU для выпонения веб приложений без написания дополнительного кода. Мотивацией для создания набора послужил ряд потребностей не решенных современными библиотеками.
- Иметь гарантию отсутствия "залипаний"
- Получить легковесную FRP-библиотеку
- Получить тесную интеграцию FRP-библиотеки с системой шаблонизации
Код на языке Scala компилируется с помощью Scala.js в язык JavaScript. Получившийся *opt.js
загружается в WebWorker с помощью очень легкого загрузчика vaska.js
и далее работает изолированно от остальной страницы. Программа в воркере шлет команды в основной поток испольнеия, управляя ими DOM моделью и получая из нее пользовательский ввод. Таким образом мы добиваемся высокой отзывчивости (отсутствия "залипаний") и отсутствия стартового лага на компиляцию JavaScipt в основном потоке исполнения.
Такой подход накладывает некоторые ограничения. В первую очередь это отсутствие возможности работать с некоторой частью API браузера на прямую. В основоном это касается самого DOM, системы событий, localStorage и т.д. Список API доступого для использования можно увидеть на этой странице. Для того, что бы использовать полный API, вам необхомо будем вызывать специальную прослойку, которая будет преобразовывать ваши вызовы в команды для vaska.js
. Посылка и итерпретация команды пожирает значительно больше ресурсов, чем прямой вызов. Вторым, неочевидным, ограничением является отсутствие возможности вызвать что либо зависимое от стека вызовов на стороне основного потока выполнения. В таким вещам относятся исключения и разнообразные функции производящие побочные эффекты вроде event.preventDefault()
. В свете выше сказанного вы не можете линковать библиотеки использующие запрещенный API в ваше Scala.js приложение.
Однако об ограничениях не стоит сильно беспокоиться. Большая часть работы программиста будет происходить с виртуальным DOM, к которому в воркере есть полный доступ. Виртуальный DOM работает значительно быстрее настоящего, а из за декларативной природы описания вьюх, мы можем оптимизировать и упаковывать команды, получая на выходе минимальное количество операций с реальным DOM.
Убедитесь что вы знакомы с языком Scala. Необходимо понимать как работают и уметь пользоваться функциями map
и flatMap
. Желательно иметь представление о нотации for-comprehensions, так как примеры будут даны именно в ней. Для начала вам необходимо сколонировать типовой проект и инпортировать в свою любимую IDE, соместимоую со Scala и SBT. Теперь давайте попробуем написать немного кода. Нашей целью станет очень простая программа, складывающая два числа, введенные в два поля ввода и выводящая результат.
import felix._
import moorka._
object Main exentds Application {
def start() = {
}
}
val firstNumber = Var("0")
val secondNumber = Var("0")
'div('class := "form"
'div('class := "form-item"
'span('class := "form-label", "First Number"),
'input('value =:= firstNumber)
),
'div('class := "form-item"
'span('class := "form-label", "Second Number"),
'input('value =:= secondNumber)
),
'div('class := "form-item"
'span('class := "form-label", "Sum"),
'textContent = for (a <- firstNumber; b <- secondNumber) yield {
try { (a.toInt + b.toInt).toString } catch { _ =>
"Please, enter valid values"
}
}
)
)
Что мы видим? Как только оба поля ввода заполнены, ответ сразу же записывается в поле Sum. Давайте размеремся, что здесь происходит. В первую очередь давайте обратим внимание на объявление реактивных переменных Rx
val firstNumber = Var("0")
val secondNumber = Var("0")
Реактивные переменные это такие объекты, которые могут сообщаться о своих изменеиях. Когда мы "присваиваем" их в DOM с помощью оператора =:=
просходит связывание значения в DOM и значения реактивной переменной. Стоит обратить внимание, что количество связываний не повляет на скорость работы приложения. Во первых они работают отдельно друг от друга, во пторых выполняются в воркере. Для основного потока это просто событие change
или input
. Теперь давайте посмотрим как они используются для выыода.
'textContent = for (a <- firstNumber; b <- secondNumber) yield {
try { (a.toInt + b.toInt).toString } catch { _ =>
"Please, enter valid values"
}
}
То что вы видите называется производным связыванием. Для значения этих двух реактивных переменных будет применеятся код написанный в блоке yield
, после чего он будет попадать в виде текста в поле Sum. В случае если сложение не получится, а оно не получится, если пользователь введет не число, будет выведена строка "Please, enter valid values".
Теперь давайте усложним пример добавив кнопку "Clear". Для этого мы немного поменяем объявление рективных переменных и добавим кое-что новое.
val clear = Channel.signal()
val (firstNumber, secondNumber) = {
val empties = clear.map(_ => "0")
(Var.withDefaultMod("0")(_ => empties), Var.withDefaultMod("0")(_ => empties))
}
...
'div('class := "form-controls"
'button('click listen clear, "Clear")
)
Мы объявили новое значение clear
и переписали опредение реактивных переменных. Сначала давайте разберемся, что такое clear
. Итак clear
это реактивный канал, который тоже является Rx
. Каналы работают так же как реактивные переменные, но не держат в себе значения. Оно появляется в них только в тот момент, когда кто-то туда что-то кладет и затем изчезает. В нашм случае кладет оператор listen
который связывет событие click
с сигналом clear
.
Следующим важным дополнением в код является withMod
. Такой способ создания реактивных переменных позволяет менять их всзязи с измениями в других Rx
. На вход withMod подается функция которая принимает текущее значение реактивной переменной и возвращает реактивное значение. В нашем случае это empties
-- производное от канала clear
. Каждый раз, когда в clear
кладут пустое значение Unit
(а listen
кладет туда именно пустое значение) в empties попадает "0". За счет withMod
эта строка перь будет подадать еще в наши реактивные переменные.
Теперь давайте возьмем более жизненный пример и напишем форму входа. Работать она будет следующим образом. Пользователь вводит адрес электронной почты и пароль и нажимает кнопку "Sign in". Запрос уходит на гипотетический сервер, ответ от которого выводится сверху формы.
val signIn = Channel.signal()
val email = Var("")
val password = Var("")
'div('class := "form"
'span(class := "form-result"
'textContent := for {
email <- email
password <- password
_ <- signIn
url = s"https://example.com/signin?email=$email,passsword=$password"
response <- Ajax.get(url).map(_.responseText).toRx
} yield {
response
}
),
'div('class := "form-item"
'span('class := "form-label", "Email"),
'input('value =:= email)
),
'div('class := "form-item"
'span('class := "form-label", "Password"),
'input('value =:= password, 'type := "password")
),
'div('class := "form-controls"
'button('click listen signIn, "Sign in")
)
)
В чем отличие от первого примера? Мы дожидаемся нажатия кнопки и ходим на сервер за ответом. Однако, если внимательно присмотреться, то мы поймем, что отличия не столь значительны. Как и в "калькуляторе" мы имеем две реактивные переменные и реактивный канал. При появлении значения Unit
в реактивном канале, мы ходим на сервер, подобно тому, как мы очищали значение полей ввода.