Binding.scala — это библиотека привязки данных для Scala, работающая как на JVM, так и на Scala.js.Binding.scala может служить основой для фреймворков пользовательского интерфейса, однако последняя версия Binding.scala 12.x больше не содержит встроенные фреймворки пользовательского интерфейса. Для создания реактивных HTML-интерфейсов рекомендуется обратиться к html.scala, который является фреймворком пользовательского интерфейса, основанным на Binding.scala, а также преемником ранее встроенной dom библиотеки.Смотрите примеры использования Binding.scala в Binding.scala • TodoMVC или ScalaFiddle DEMOs для выполнения типичных задач при работе с Binding.scala.
Binding.scala и html.scala имеют больше возможностей и меньше концепций, чем другие реактивные веб-фреймворки, такие как ReactJS.
Binding.scala | ReactJS | |
---|---|---|
Поддерживает ли литералы HTML? | Да | Частично поддерживается. Обычный HTML не компилируется, если разработчики не заменят атрибуты class и for на className и htmlFor , и не преобразуют встроенные style из синтаксиса CSS в синтаксис JSON вручную. |
Алгоритм обновления DOM | Точное привязывание данных, которое быстрее, чем виртуальный DOM | Виртуальное дерево DOM, что требует управления атрибутами key вручную для сложных DOM. |
Управление жизненным циклом выражений привязки данных | Автоматически | Недоступно |
Статическая проверка типов | Да, даже для HTML-тэгов и атрибутов | Нет |
Кривая обучения | Всегда легко | Легко начать. Требуется много усилий для понимания его специальных случаев. |
Дополнительную информацию см. в разделе Проектирование.
Мы создадим веб-страницу с использованием Binding.scala во время следующих шагов.### Шаг 0: Настройка проекта Sbt Scala.js
См. http://www.scala-js.org/tutorial/basic/ для информации о том, как настроить такой проект.
build.sbt
:// Активируйте макросные аннотации, установив флаги компилятора Scala для версий 2.13 и выше
scalacOptions ++= {
import Ordering.Implicits._
if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
Seq("-Ymacro-annotations")
} else {
Nil
}
}
// Активируйте макросные аннотации, добавив плагины компилятора для версий 2.12 и ниже
libraryDependencies ++= {
import Ordering.Implicits._
if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
Nil
} else {
Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
}
}
библиотека依存关系 += "org.lrng.binding" %%% "html" % "latest.release"
### Шаг 2: Создайте поле `data`, которое будет содержать некоторые `Var` и `Vars` как источник данных для привязки
```scala
case class Contact(имя: Var[String], email: Var[String])
val данные = Vars.empty[Contact]
Один Var
представляет собой привязываемую переменную,
которая также реализует характеристику Binding
,
поэтому один Var
можно рассматривать как привязывающее выражение.
Если другое выражение зависит от одного Var
, то значение этого выражения изменится всякий раз, когда значение Var
изменится.
Один Vars
представляет собой последовательность привязываемых переменных,
которая также реализует характеристику BindingSeq
,
поэтому один Vars
можно рассматривать как последовательность привязывающих выражений.
Если другое выражение зависит от одного Vars
, то значение этого выражения изменится всякий раз, когда значение Vars
изменится.
@html
, который содержит привязывающие выражения@html
def таблица: Binding[Table] = {
<table border="1" cellPadding="5">
<thead>
<tr>
<th>Имя</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{
for (contact <- данные) yield {
<tr>
<td>{contact.имя.bind}</td>
<td>{contact.email.bind}</td>
</tr>
}
}
</tbody>
</table>
}
```Один `@html` метод представляет собой реактивный XHTML шаблон, поддерживающий HTML текст. В отличие от обычных XML текстов, используемых в стандартных Scala методах, тип HTML текста — это конкретный подкласс `org.scalajs.dom.raw.Node` или `com.thoughtworks.binding.BindingSeq[org.scalajs.dom.raw.Node]`, а не `scala.xml.Node` или `scala.xml.NodeSeq`.
Поэтому мы можем использовать следующий код внутри `@html` метода:
```scala
@html def нод: Binding[org.scalajs.dom.raw.HTMLBRElement] = <br/>
и
@html def нод: Binding[BindingSeq[org.scalajs.dom.raw.HTMLBRElement]] = <br/><br/>
Метод @html
может быть объединён с другими данными-биндингами двумя способами:
bind
внутри @html
метода для получения значений других Binding
.for
/ yield
для отображения одного BindingSeq
в другой.Вы можете встраивать Node
или BindingSeq[Node]
в другие HTML элементы текста с помощью интерполяции {...}
.
main
@JSExport
def main(): Unit = {
html.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
пустой в данный момент.
data
данными для таблицы```scala@html def table: BindingSeq[Node] = {
Name | Action | {contact.name.bind} | {contact.email.bind} | contact.name.value = "Modified name" } > Modify name | } }
---|
А при нажатии на кнопку Изменить имя поле name
контакта меняется,
и Binding.scala изменяет содержимое соответствующей строки <tr>
на новое значение поля name
.
ReactJS требует от пользователей предоставления функции render
для каждого компонента.
Функция render
должна преобразовать props
и state
в виртуальный DOM ReactJS,
после чего фреймворк ReactJS создаёт DOM с такой же структурой, как и виртуальный DOM. Когда меняется state
, фреймворк ReactJS вызывает функцию render
, чтобы получить новый виртуальный DOM.
К сожалению, фреймворк ReactJS точно не знает, что именно изменилось в state
.
Фреймворк ReactJS должен сравнить новый виртуальный DOM с исходным виртуальным DOM,
предположить изменения между ними,
а затем применить эти предполагаемые изменения к реальному DOM. Например, после того как вы добавили строку таблицы <tr>
в существующий <tbody>
внутри <table>
,
ReactJS может считать, что вы также изменили содержимое каждой существующей строки <tr>
внутри <tbody>
.Причина этого заключается в том, что функция render
для ReactJS не описывает взаимоотношения между состоянием (state
) и DOM.
Вместо этого она описывает процесс создания виртуального DOM.
Как следствие, функция render
не предоставляет никакой информации о цели изменения состояния,
хотя это было бы полезно для системы связывания данных.В отличие от ReactJS, метод @html
в Binding.scala НЕ является обычной функцией.
Это шаблон, который описывает взаимоотношения между источником данных и DOM.
Когда часть источника данных изменяется, Binding.scala знает о точном соответствующем частичном DOM, затронутом этим изменением,
поэтому он переопределяет только ту часть метода @html
, которая отражает изменение в DOM.
С помощью возможности точного связывания данных, предоставляемой Binding.scala,
вы можете избавиться от концепций, используемых для указания алгоритма догадок ReactJS,
таких как атрибут key
, метод shouldComponentUpdate
, метод componentDidUpdate
или метод componentWillUpdate
.
Наименьшая компонуемая единица в ReactJS — это компонент. Можно сказать, что React-компонент легче, чем контроллер AngularJS, в то время как Binding.scala превосходит даже это.
Наименьшая компонуемая единица в Binding.scala — это метод @html
.
Каждый метод @html
способен компоноваться с другими методами @html
через .bind
.
case class Contact(name: Var[String], email: Var[String])
@html
def bindingButton(contact: Contact): Binding[Button] = {
<button
onclick={ event: Event =>
contact.name.value = "Изменённое имя"
}
>
Изменить имя
</button>
}
@html
def bindingTr(contact: Contact): Binding[TableRow] = {
<tr>
<td>{ contact.name.bind }</td>
<td>{ contact.email.bind }</td>
<td>{ bindingButton(contact).bind }</td>
</tr>
}
@html
def bindingTable(contacts: BindingSeq[Contact]): Binding[Table] = {
<table>
<tbody>
{
for (contact <- contacts) yield {
bindingTr(contact).bind
}
}
</tbody>
</table>
}
``````javascript
@JSExport
function main() {
var data = Vars(Contact(Var("Yang Bo"), Var("yang.bo@rea-group.com")));
dom.render(document.body, bindingTable(data));
}
props
в ReactJS вы просто предоставляете параметры для Binding.scala.propTypes
в ReactJS вы просто определяете типы параметров в Binding.scala.props
в ReactJS, вы проверяете типы на этапе компиляции.Способность точной привязки данных в Binding.scala требует регистрации прослушивателей на источнике данных. Другие реактивные фреймворки, имеющие такую возможность, заставляют пользователей управлять жизненным циклом выражений привязки данных.
Например, MetaRx предоставляет метод dispose
для отмены регистрации прослушивателей, созданных при построении выражений привязки данных.
Пользователи MetaRx должны вызывать метод dispose
после каждого изменения выражения для каждого map
и flatMap
.
Иначе MetaRx будет утечкой памяти. К сожалению, ручное управление всеми прослушивателями слишком сложно для сложных выражений привязки данных.
Еще один реактивный веб-фреймворк Widok не предоставил никаких механизмов управления жизненным циклом выражений привязки данных. Как следствие, он всегда утекает память.В Binding.scala, в отличие от MetaRx или Widok, все выражения привязки данных являются чистыми функциональными, без побочных эффектов. Binding.scala не регистрирует никаких прослушивателей при создании отдельных выражений, поэтому пользователям не требуется ручное отменение регистрации прослушивателей для одного выражения, как в случае с MetaRx.
Вместо этого Binding.scala создает все внутренние прослушиватели вместе, когда пользователь вызывает dom.render
или Binding.watch
для корневого выражения. Обратите внимание, что dom.render
или Binding.watch
управляют прослушивателями на всех входящих выражениях, а не только на прямых прослушивателях корневого выражения.
Кратко говоря, Binding.scala разделена на две категории функциональности:
@html
, которые производят чистые функциональные выражения без побочных эффектов.dom.render
или Binding.watch
, которые автоматически управляют всеми побочными эффектами.Как видно, вы можете встраивать литеральные значения HTML в методах @html
в файлах исходного кода Scala. Вы также можете встраивать выражения Scala в фигурных скобках в содержании или атрибутах значений литерального значения HTML.
@html
def уведомление_ящик(сообщение: String): Binding[Div] = {
<div class="uvedomlenie" title={ s"Подсказка: $сообщение" }>
{
сообщение
}
</div>
}
```Несмотря на схожий синтаксис литералов HTML между Binding.scala и ReactJS,
Binding.scala создаёт реальный DOM вместо виртуального DOM ReactJS.
В приведённом выше примере, `<div>...</div>` создаёт элемент DOM типа `org.scalajs.dom.html.Div`.
Затем, магия `@html` позволяет методу обёрнуть результат как `Binding`.
Вы можете даже назначить `Div` локальной переменной и вызывать нативные методы DOM на этой переменной:
```scala
@html
def уведомление_ящик(сообщение: String): Binding[Div] = {
val result: Div = <div class="uvedomlenie" title={ s"Подсказка: $сообщение" }>
{
сообщение
}
</div>
result.scrollIntoView()
result
}
Метод scrollIntoView
будет вызван при создании Div
.
Если вы вызываете другой метод, который не определён в Div
, компилятор Scala сообщит об ошибке во время компиляции, а не приведёт к ошибке выполнения,
поскольку Scala — это статически типизированный язык, и компилятор Scala понимает тип Div
.
Вы также можете заметить class
и title
. Они являются свойствами DOM или атрибутами HTML на Div
.
Они тоже проверяются на соответствие типам компилятором Scala.
Например, дан следующий метод typo
:
@html
def typo = {
val myDiv = <div typoProperty="xx">content</div>
myDiv.typoMethod()
myDiv
}
Компилятор Scala сообщит об ошибках такого типа:
typo.scala:23: значение typoProperty не является членом org.scalajs.dom.html.Div
val myDiv = <div typoProperty="xx">content</div>
^
typo.scala:24: значение typoMethod не является членом org.scalajs.dom.html.Div
myDiv.typoMethod()
^
```С помощью статической системы типов, методы `@html` могут быть гораздо более надёжными, чем компоненты ReactJS.
Вы можете найти полный список поддерживаемых свойств и методов на [scaladoc scalajs-dom](http://www.scala-js.org/api/scalajs-dom/0.8/org/scalajs/dom/raw/HTMLElement.html) или [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement).
#### Пользовательские атрибуты
Если вы хотите отключить статическую проверку типов атрибутов, добавьте префикс `data:` к атрибуту:
```scala
@html def myCustomDiv = {
val myDiv = <div data:customAttributeName="attributeValue"></div>
assert(myDiv.getAttribute("customAttributeName") == "attributeValue")
myDiv
}
Компилятор Scala теперь не выдаёт ошибок.## Примеры использования* TodoMVC: проект, который предлагает ту же Todo-приложение, реализованное с использованием концепций MV*, в большинстве популярных сегодня JavaScript MV*-фреймворков.
Binding.scala имеет крайне небольшую базу кода. Исходные файлы разделены на несколько библиотек, один файл на каждую библиотеку.
Этот модуль доступен как для JVM, так и для Scala.js. Вы можете добавить его в ваш build.sbt
.
// Для проектов на JVM
libraryDependencies += "com.thoughtworks.binding" %% "binding" % "latest.release"
// Для проектов Scala.js или кросс-проектов JS/JVM
libraryDependencies += "com.thoughtworks.binding" %%% "binding" % "latest.release"
Это новая библиотека шаблонизации HTML, основанная на именной основе XML литералов, модуль доступен только для Scala.js, а версия Scala должна находиться между 2.12 и 2.13. Вы можете добавить его в ваш build.sbt
.
// Включите макросные аннотации, установив флаги компилятора для Scala 2.13
scalacOptions ++= {
import Ordering.Implicits._
if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
Seq("-Ymacro-annotations")
} else {
Nil
}
}
// Включите макросные аннотации, добавив плагины компилятора для Scala 2.12
libraryDependencies ++= {
import Ordering.Implicits._
if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
Nil
} else {
Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
}
}
// Для проектов Scala.js (Scala 2.12 - 2.13)
libraryDependencies += "org.lrng.binding" %%% "html" % "latest.release"
Дополнительная информация доступна по адресу html.scala.
scala.concurrent.Future
(FutureBinding.scala)Этот модуль доступен как для JVM, так и для Scala.js. Вы можете добавить его в ваш build.sbt
.// Для проектов на JVM
libraryDependencies += "com.thoughtworks.binding" %% "futurebinding" % "latest.release"
// Для проектов Scala.js или кросс-проектов JS/JVM
libraryDependencies += "com.thoughtworks.binding" %%% "futurebinding" % "latest.release"
Дополнительная информация доступна по адресу FutureBinding.
Promise
(JsPromiseBinding.scala)Этот модуль доступен только для проектов Scala.js. Вы можете добавить его в ваш build.sbt
.
// Для проектов Scala.js
libraryDependencies += "com.thoughtworks.binding" %%% "jspromisebinding" % "latest.release"
Дополнительную информацию можно найти в JsPromiseBinding.
Из-за изменений в коллекционной API, версия Binding.scala 12.x работает только с Scala 2.13, ориентированной на JVM, Scala.js 0.6 и Scala.js 1.x.
Для Scala 2.10, 2.11 и 2.12 на JVM или Scala.js 0.6 используйте версию Binding.scala 11.x.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )