Binding.scala — это фреймворк для работы с данными, написанный на языке Scala, который работает как на JVM, так и на Scala.js.Binding.scala можно использовать как реактивный веб-фреймework. Он позволяет создавать реактивные узлы DOM с использованием нативной синтаксической конструкции XHTML, которая автоматически изменяется при изменении источника данных.Для использования Binding.scala рекомендуются примеры Binding.scala • TodoMVC или другие ДЕМО, которые содержат реализацию многих распространённых функций.
Сравнение с другими реактивными веб-фреймворками (например, ReactJS) показывает, что Binding.scala имеет больше возможностей и меньше концепций.
Binding.scala | ReactJS | |
---|---|---|
Поддержка HTML синтаксиса | Поддерживается | Частично поддерживается. Обычный HTML не компилируется, если разработчики не переопределяют атрибуты <code>class</code> и <code>for</code> как <code>className</code> и <code>htmlFor</code> , а также не преобразуют синтаксис внутреннего стиля CSS в JSON. |
Алгоритмы обновления DOM | Точная привязка данных, быстрее, чем виртуальный DOM | Виртуальные DOM имеют различия, при работе с сложными DOM требуется вручную управлять свойством <code>key</code> . |
Управление жизненным циклом выражений привязки данных | Полностью автоматизировано | Отсутствует |
Статическая типизация | Поддерживается, даже для HTML-тэгов и атрибутов | Неподдерживаемо |
Для получения более подробной информации обратитесь к разделу Проектирование.## Начало работы
Мы создадим страницу Binding.scala в следующих шагах.
Подробная информация доступна по адресу http://www.scala-js.org/tutorial/basic/.
build.sbt
libraryDependencies += "com.thoughtworks.binding" %%% "dom" % "latest.release"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
Var
и Vars
в качестве источников данных для ваших выражений привязки данныхcase class Contact(name: Var[String], email: Var[String])
val data = Vars.empty[Contact]
Один Var
представляет собой привязываемую переменную,
при этом он реализует характеристики Binding
,
что позволяет рассматривать его как выражение привязки данных.
Если другое выражение привязки данных зависит от Var
, то значение этого выражения будет меняться при изменении значения Var
.
Один Vars
представляет собой последовательность привязываемых переменных,
при этом он также реализует характеристику BindingSeq
,
что означает, что один Vars
может рассматриваться как последовательность выражений привязки данных.
Если другое выражение привязки данных зависит от одного Vars
,
то значение этого выражения будет меняться в зависимости от изменения значений Vars
.
@dom
, содержащего выражение привязки данных```scala@dom def table: Binding[Table] = {
Имя | {контакт.имя.bind} | {контакт.email.bind} | } }
---|
Тип его возвращаемого значения всегда упакован в характеристику com.thoughtworks.binding.Binding
.
Например, @dom def x: Binding[Int] = 1
, @dom def message: Binding[String] = "content"
.
Методы @dom
поддерживают синтаксис HTML.
В отличие от обычных методов Scala, использующих синтаксис XML,
синтаксис HTML имеет тип org.scalajs.dom.raw.Node
или подтип com.thoughtworks.binding.BindingSeq[org.scalajs.dom.raw.Node]
,
а не scala.xml.Node
или scala.xml.NodeSeq
.
Поэтому мы пишем такие строки кода, как @dom def node: Binding[org.scalajs.dom.raw.HTMLBRElement] = <br/>
или @dom def nodes: Binding[BindingSeq[org.scalajs.dom.raw.HTMLBRElement]] = <br/><br/>
.
Методы @dom
, составленные из других выражений связывания данных, могут быть созданы двумя способами:
bind
внутри метода @dom
, чтобы получить значения других Binding
.for
или yield
внутри метода @dom
, чтобы отображать BindingSeq
в другие выражения.Вы можете использовать синтаксис вставки {...}
для внедрения Node
или BindingSeq[Node]
внутрь других элементов HTML.
main
@JSExport
def main(): Unit = {
dom.render(document.body, table)
}
main
на HTML-странице<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="js-fastopt.js"></script>
</head>
<body>
<script type="text/javascript">
SampleMain().main()
</script>
</body>
</html>
К этому моменту вы должны видеть пустую таблицу с заголовками, так как data
в данный момент пуст.### Шаг 6: Добавление нескольких кнопок для заполнения данных таблицы
@dom
def table: Binding[BindingSeq[Node]] = {
<div>
<button
onclick={ event: Event =>
data.get += Contact(Var("Yang Bo"), Var("yang.bo@rea-group.com"))
}
>
Добавить контакт
</button>
</div>
<table border="1" cellpadding="5">
<thead>
<tr>
<th>Имя</th>
<th>Email</th>
<th>Действие</th>
</tr>
</thead>
<tbody>
{
for (contact <- data) yield {
<tr>
<td>
{contact.name.bind}
</td>
<td>
{contact.email.bind}
</td>
<td>
<button
onclick={ event: Event =>
contact.name := "Изменённое имя"
}
>
Изменить имя
</button>
</td>
</tr>
}
}
</tbody>
</table>
}
При нажатии на кнопку "Добавить контакт", новый контакт будет добавлен в data
.
При этом, благодаря Binding.scala, которая знает связь между DOM и data
, будет добавлена новая строка <tr>
со всеми данными нового контакта.
При нажатии на кнопку "Изменить имя", имя соответствующего контакта будет изменено,
так как Binding.scala изменяет значение имени в соответствующей строке <tr>
.
Полный пример можно найти по адресу https://github.com/ThoughtWorksInc/Binding.scala-sample.
ReactJS требует от пользователя предоставления функции render
для каждого компонента.
Функция render
должна преобразовать props
и state
в виртуальный DOM ReactJS,
после чего фреймворк ReactJS создаёт реальный DOM, основываясь на структуре виртуального DOM.При изменении state
, ReactJS вызывает функцию render
, чтобы получить новый виртуальный DOM.
Однако, к сожалению, ReactJS не может точно знать, что именно было изменено в state
.
Поэтому ReactJS сравнивает новый виртуальный DOM с предыдущим, чтобы определить изменения,
и применяет эти изменения к реальному DOM.
Например, если вы добавили строку <tr>
в начало <tbody>
внутри <table>
,
ReactJS мог бы ошибочно предположить, что были изменены все строки <tr>
внутри <tbody>
и добавлена ещё одна строка <tr>
в конец. Причина заключается в том, что функция render
в ReactJS не описывает отношения между состоянием (state
) и DOM.
Наоборот, она представляет процесс создания виртуального DOM.
Другими словами, хотя как фреймворк данных связывания он бесспорно нуждается в информации о изменениях состояния, функция render
в ReactJS не предоставляет такую информацию.
В отличие от ReactJS, метод @dom
в Binding.scala не является обычной функцией.
Это шаблон, который описывает отношение между источником данных и DOM.
Когда происходит частичное изменение источника данных, Binding.scala может определить конкретные части DOM, соответствующие этим изменениям.
Поэтому Binding.scala требует лишь частичного пересчета метода @dom
, чтобы получить соответствующий ему фрагмент DOM.С помощью точной способности привязки данных, которую предоставляет Binding.scala, вы можете избавиться от ненужных концепций, таких как алгоритмы догадок, используемые в ReactJS,
например свойства key
, методы shouldComponentUpdate
, componentDidUpdate
и componentWillUpdate
.### Модульность
Минимальная единица в ReactJS — это компонент.
Без сомнения, React-компоненты легче, чем контроллеры AngularJS, но Binding.scala ещё лучше.
Минимальная единица в Binding.scala — это просто метод @dom
.
Каждый метод @dom
имеет возможность объединяться с другими методами @dom
через .bind
.
случайный класс Контакт(имя: Var[String], электронная_почта: Var[String])
@dom
определение bindingButton(контакт: Контакт): Binding[Button] = {
<button
onclick={ событие: Event =>
контакт.имя := "Изменённое имя"
}
>
Изменить имя
</button>
}
@dom
определение bindingTr(контакт: Контакт): Binding[TableRow] = {
<tr>
<td>{ контакт.имя.bind }</td>
<td>{ контакт.электронная_почта.bind }</td>
<td>{ bindingButton(контакт).bind }</td>
</tr>
}
@dom
определение bindingTable(контакты: BindingSeq[Контакт]): Binding[Table] = {
<table>
<tbody>
{
для (контакт <- контакты) yield {
bindingTr(контакт).bind
}
}
</tbody>
</table>
}
@JSExport
определение основное(): Единица = {
значение данных = Vars(Контакт(Var("Янг Бо"), Var("янг.бо@реа-групп.ком")))
dom.render(document.body, bindingTable(данных))
}
Вы заметите, что этот подход проще, чем в ReactJS, поскольку:
props
。propTypes
。Другие реактивные фреймворки также предоставляют такую возможность, но зачастую требуется от пользователя самостоятельно управлять жизненным циклом привязки данных.
Например, MetaRx предоставляет метод dispose
, который используется для отмены регистрации слушателя.
Пользователи MetaRx обязаны вызывать метод dispose
после каждого использования методов map
и flatMap
, чтобы избежать утечек памяти при изменении выражения.
Однако управление жизненным циклом становится особенно сложным для сложных выражений привязки данных, где каждый вызов dispose
должен быть сделан вручную.
Ещё один реактивный веб-фреймворк Widok не предлагает никаких механизмов для управления жизненным циклом выражений привязки данных. Это приводит к постоянной утечке памяти.
В отличие от MetaRx и Widok, все выражения привязки данных в Binding.scala являются чистыми функциональными, без побочных эффектов.
Binding.scala автоматически регистрирует слушателей при создании выражений, поэтому нет необходимости вручную отменять регистрацию слушателей, как это делается в MetaRx.Когда пользователь вызывает dom.render
или Binding.watch
в корневом выражении, Binding.scala одновременно создает всех необходимых слушателей, а не только непосредственно для корневого выражения.Иными словами, Binding.scala делит функции на две категории:
@dom
.dom.render
и Binding.watch
, которые автоматически управляют всеми побочными эффектами.Как вы можете видеть, грамматика HTML может быть встроена в методах @dom
в файлах исходного кода Scala.
Вы также можете использовать Scala-код внутри фигурных скобок {...}
или значений атрибутов в HTML-тегах.
@dom
def notificationBox(message: String): Binding[Div] = {
<div class="notification" title={ s"Подсказка: $message" }>
{
message
}
</div>
}
Хотя грамматика HTML в Binding.scala и ReactJS имеют некоторые схожие черты, Binding.scala создаёт реальный DOM, а не виртуальный DOM, как это делает ReactJS.
В приведённом выше примере <div>...</div>
создаёт элемент DOM типа org.scalajs.dom.html.Div
.
Затем волшебство аннотации @dom
оборачивает возвращаемое значение в тип Binding
. Вы можете присвоить Div
локальной переменной и вызывать на нём нативные методы DOM:
@dom
def notificationBox(сообщение: String): Binding[Div] = {
val result: Div = <div class="уведомление" title={ s"Подсказка: $сообщение" }>
{
сообщение
}
</div>
result.scrollIntoView()
result
}
Метод scrollIntoView()
будет вызван сразу после создания Div
.
Если вы вызываете метод, который не определён в Div
, компилятор Scala вернёт ошибку компиляции, а не ошибку выполнения.
Это связано с тем, что Scala — это язык со статической типизацией, и компилятор Scala понимает тип Div
.Вы также заметили class
и title
. Это свойства DOM или HTML-атрибуты div
.
Они тоже проверяются компилятором Scala.
Например, если вы используете следующий метод typo
:
@dom
def typo = {
val myDiv = <div typoProperty="xx">контент</div>
myDiv.typoMethod()
myDiv
}
Компилятор Scala вернёт такие ошибки:
typo.scala:23: значение typoProperty не является частью org.scalajs.dom.html.Div
val myDiv = <div typoProperty="xx">контент</div>
^
typo.scala:24: значение typoMethod не является частью org.scalajs.dom.html.Div
myDiv.typoMethod()
^
С помощью системы статического типа, методы @dom
могут иметь большую надёжность по сравнению с компонентами ReactJS.
Вы можете найти полный список поддерживаемых атрибутов и методов на страницах Scaladoc библиотеки scalajs-dom
или MDN.
Если вы хотите отключить статическую типизацию атрибутов, добавьте префикс data:
перед атрибутом:
@dom def мойПользовательскийDiv = {
val мойDiv = <div data:пользовательскиеПоля="значениеАтрибута"></div>
assert(мойDiv.getAttribute("пользовательскиеПоля") == "значениеАтрибута")
мойDiv
}
Теперь компилятор Scala не вернёт ошибку.
Binding.scala имеет очень маленький набор кода. Исходный код разделён на четыре библиотеки, каждая из которых находится в своём файле.
Этот модуль может использоваться как на JVM, так и на Scala.js.
Вы можете добавить его в ваш build.sbt
.```scala
// Для проектов на JVM
libraryDependencies += "com.thoughtworks.binding" %% "binding" % "latest.release"
Добавляем плагин компилятора:
```scala
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
Этот модуль предназначен только для проектов Scala.js. Вы можете добавить его в ваш build.sbt
.
// Для проектов Scala.js или проектов, работающих как на JVM, так и на JavaScript
libraryDependencies += "com.thoughtworks.binding" %%% "dom" % "latest.release"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
scala.concurrent.Future
(FutureBinding.scala)Этот модуль можно использовать как на JVM, так и на Scala.js. Вы можете добавить его в ваш build.sbt
.
// Для проектов на JVM
libraryDependencies += "com.thoughtworks.binding" %% "futurebinding" % "latest.release"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
// Для проектов Scala.js или проектов, работающих как на JVM, так и на JavaScript
libraryDependencies += "com.thoughtworks.binding" %%% "futurebinding" % "latest.release"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
Promise
(JsPromiseBinding.scala)Этот модуль предназначен только для проектов Scala.js. Вы можете добавить его в ваш build.sbt
.
// Для проектов Scala.js или проектов, работающих как на JVM, так и на JavaScript
libraryDependencies += "com.thoughtworks.binding" %%% "jspromisebinding" % "latest.release"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )