Skip to content

Binding.scala快速上手指南

杨博 (Yang Bo) edited this page Feb 4, 2017 · 12 revisions

基本用法

Binding.scala 的基本用法很像 ReactJS ,也是通过编写模板把数据映射成 HTML 。

第零步:构建脚本配置

首先,你需要设置项目的构建脚本。Binding.scala 就是普通的 Scala.js 库,按普通的 Scala.js 项目配置方式即可。具体参考 Scala.js 的教程或者TodoMVC的源码仓库

第一步:添加 Binding.scala 的依赖

修改你的 build.sbt 文件,加入以下两行

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

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

第二步:定义 HTML 模板

@dom
def spinner(i: Var[Int]) = {
  <div>
    <button onclick={ event: Event => i := i.get - 1 }>-</button>
    { i.bind.toString }
    <button onclick={ event: Event => i := i.get + 1 }>+</button>
  </div>
}

@dom
def render = {
  val i = Var(0)
  <div>
    { spinner(i).bind }
    控件当前值是:{ i.bind.toString }
  </div>
}

你可以在ScalaFiddle实际运行一下试试。

Scala 代码的语法和 ECMAScript 6 很接近。有 JavaScript 经验的同学只要参考 Scala.js for JavaScript developers 就可以迁移到 Scala.js 了。

以上 Scala 代码创建了两个 HTML 模板,其中 spinner 是个数字选择组件,render 是页面的内容。Binding.scala 支持 Scala 代码和 HTML 模板混合编写,所以 onclick={...} 把一个 Scala 函数设置给了 HTML 属性 onclick

上述代码用到了 Binding.scala 的几个API:

  • @dom 把方法标记成绑定表达式,在这个方法内可以编写 HTML 模板、可以使用 .bind 监听其他 HTML 模板或变量。
  • Binding 是绑定表达式。上面代码的 spinner(i)renderi 都是 Binding
    • Binding.bind 用来监听绑定表达式。

      xxx.bind的含义是“each time xxx changes”。比如 @dom def j = i.bind + 5 表示每当 i 改变时,j也改变,j的值是 i 的值加5。

      注意:bind表示绑定表达式之间的依赖关系,所以只能写在绑定表达式的内部。bind不是普通的函数!

  • Var 是可以被监听的变量,Var继承了Binding。上面代码中的 i 就是个 Var
    • Var.bind 继承自 Binding.bind,表示要监听变量的值。
    • Var.get 获取变量的当前值,不能写在绑定表达式中。
    • := 对变量的赋值,并重新计算所有依赖该变量的绑定表达式,不能写在绑定表达式中。

注意:{ event: Event => i := i.get + 1 }没加 @dom ,是个普通的函数而不是绑定表达式,所以其中可以使用 get:=

这两个 HTML 模板之间传递了绑定变量 i 作为参数。所以,当 spinner 内的 onclick 修改 i 时,spinnerrender 中的 i.bind 都会触发,实现了类似双向绑定的效果。

第三步:渲染 HTML

编写完功能后,你需要在 main 中把 render 渲染出来。

@JsExport def main() = {
  dom.render(document.body, render)
}
  • dom.render 接受两个参数,第一个参数表示要把模板渲染到哪个位置,第二个参数是将要渲染的模板。

最后在静态 HTML 中调用 main() 方法:

<script type="text/javascript"> SampleMain().main() </script>

这样一来整个程序就写完了。

容器和循环

复杂的网页上往往会有重复的元素,可以把数据保存到 Vars 容器,然后用 for / yield 渲染。

比如

@dom def render = {
  val logs = Vars("Hello", "Binding.scala")
  <div>
    { for { log <- logs } yield <div>{ log }</div> }
  </div>
}

以上代码通过 for / yield 语句渲染了两行文字,你可以在ScalaFiddle实际运行一下试试

  • Vars 是可以被监听的容器。所以,每当 Vars 的内容修改时,渲染出的网页也会自动修改:
@dom def render = {
  val logs = Vars("Input code:")
  val keyDownHandler = { event: KeyboardEvent =>
    (event.currentTarget, event.keyCode) match {
      case (input: html.Input, KeyCode.Enter) =>
        event.preventDefault()
        logs.get += input.value
        logs.get += js.eval(input.value).toString
        input.value = ""
      case _ =>
    }
  }
  <div>
    { for { log <- logs } yield <div>{ log }</div> }
    <input type="text" onkeydown={ keyDownHandler }/>
  </div>
}

以上代码实现了一个代码控制台,你可以在输入框中输入代码,按下回车键,就能看到代码执行结果。 你可以在ScalaFiddle实际运行一下试试。

Vars.get 类似 Var.get,用来获取容器的当前值,返回值是个 scala.collection.mutable.Buffer。 所以你还可以对 Var.get 取得的当前值调用 Buffer+= 等各种方法,直接修改容器的当前值。 当你修改这个 Buffer 时,会触发 Binding.scala 自动执行对应操作,把修改集映射到 for / yield 生成的页面上。

比如,logs.get += input.value 添加了一个字符串,那么渲染出的网页上也会添加一行包含该字符串的 <div> ,而不需要修改网页上原有的其他部分。

注意,Vars.get+= 就像 Var.get:= 一样,也不能在@dom绑定表达式或代码模板中调用,而只能在其他函数中调用。 比如以上代码的 submitHandler 回调函数不是一个 @dom 函数,所以可以在其中修改 logs