1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/thoughtworks-Binding.scala

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
README-zh.md 27 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 16.03.2025 00:12 a08cfa0

Binding.scala

Присоединиться к чату в https://gitter.im/ThoughtWorksInc/Binding.scala Статус сборки Maven Central (основная функциональность) Maven Central (интеграция с DOM) Maven Central (удалённое привязывание данных для scala.concurrent.Future) Maven Central (удалённое привязывание данных для ECMAScript 2015 Promise)

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 в следующих шагах.

Шаг 0: Настройка проекта Sbt Scala.js

Подробная информация доступна по адресу http://www.scala-js.org/tutorial/basic/.

Шаг 1: Добавьте зависимость Binding.scala в ваш build.sbt

libraryDependencies += "com.thoughtworks.binding" %%% "dom" % "latest.release"

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

Шаг 2: Создайте область данных, содержащую несколько 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.

Шаг 3: Создание метода @dom, содержащего выражение привязки данных```scala

@dom def table: Binding[Table] = {

{ for (контакт } }
Имя E-mail
{контакт.имя.bind} {контакт.email.bind}
} ```Метод `@dom` представляет собой выражение связывания данных.

Тип его возвращаемого значения всегда упакован в характеристику 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, составленные из других выражений связывания данных, могут быть созданы двумя способами:

  1. Вы можете использовать метод bind внутри метода @dom, чтобы получить значения других Binding.
  2. Вы можете использовать выражения for или yield внутри метода @dom, чтобы отображать BindingSeq в другие выражения.

Вы можете использовать синтаксис вставки {...} для внедрения Node или BindingSeq[Node] внутрь других элементов HTML.

Шаг 4: Отображение выражения связывания данных в DOM в методе main

@JSExport
def main(): Unit = {
  dom.render(document.body, table)
}

Шаг 5: Вызов метода 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, поскольку:

  • Вы можете просто передавать параметры в Binding.scala,而不必像在ReactJS中那样传递 props
  • В Binding.scala вы можете просто определять типы параметров,而不必像在ReactJS中那样指定 propTypes
  • Вы можете выполнять проверку типов на этапе компиляции,而不必像在ReactJS中那样只能在运行时遇到异常。

Управление жизненным циклом выражений данных привязкиБез сомнения, точность управления данными в Binding.scala требует регистрации слушателей для источников данных.

Другие реактивные фреймворки также предоставляют такую возможность, но зачастую требуется от пользователя самостоятельно управлять жизненным циклом привязки данных.

Например, 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 и статическая проверка типов

Как вы можете видеть, грамматика 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 имеет очень маленький набор кода. Исходный код разделён на четыре библиотеки, каждая из которых находится в своём файле.

Ядро выражений данных (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)

Интеграция с HTML DOM (dom.scala)

Этот модуль предназначен только для проектов 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)

Данные для ECMAScript 2015's 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)

Другие ссылки* Документация по API

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/thoughtworks-Binding.scala.git
git@api.gitlife.ru:oschina-mirror/thoughtworks-Binding.scala.git
oschina-mirror
thoughtworks-Binding.scala
thoughtworks-Binding.scala
master