威尼斯使命未经作者许可。

HTML也可以静态编译?

2016/11/30 · HTML5 · 1
评论 ·
binding.scala,
React,
前端

本文作者: 伯乐在线 –
ThoughtWorks
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

More than React系列文章:

《More than
React(一)为什么ReactJS不适合复杂的前端项目?》

《More than
React(二)React.Component损害了复用性?》

《More than React(三)虚拟DOM已死?》

《More than
React(四)HTML也可以静态编译?》


《More than
React》系列的上一篇文章《虚拟DOM已死?》比较了Binding.scala和其他框架的渲染机制。本篇文章中将介绍Binding.scala中的XHTML语法。

其他前端框架的问题

对HTML的残缺支持

以前我们使用其他前端框架,比如Cycle.js
、Widok、ScalaTags时,由于框架不支持
HTML语法,前端工程师被迫浪费大量时间,手动把HTML改写成代码,然后慢慢调试。

就算是支持HTML语法的框架,比如ReactJS,支持状况也很残缺不全。

比如,在ReactJS中,你不能这样写:

JavaScript

class BrokenReactComponent extends React.Component { render() { return (
<ol> <li class=”unsupported-class”>不支持 class
属性</li> <li style=”background-color: red”>不支持 style
属性</li> <li> <input type=”checkbox”
id=”unsupported-for”/> <label for=”unsupported-for”>不支持 for
属性</label> </li> </ol> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BrokenReactComponent extends React.Component {
  render() {
    return (
      <ol>
        <li class="unsupported-class">不支持 class 属性</li>
        <li style="background-color: red">不支持 style 属性</li>
        <li>
          <input type="checkbox" id="unsupported-for"/>
          <label for="unsupported-for">不支持 for 属性</label>
        </li>
      </ol>
    );
  }
}

前端工程师必须手动把 classfor 属性替换成 className
htmlFor,还要把内联的 style
样式从CSS语法改成JSON语法,代码才能运行:

JavaScript

class WorkaroundReactComponent extends React.Component { render() {
return ( <ol> <li className=”workaround-class”>被迫把 class
改成 className</li> <li style={{ backgroundColor: “red”
}}>被迫把样式表改成 JSON</li> <li> <input
type=”checkbox” id=”workaround-for”/> <label
htmlFor=”workaround-for”>被迫把 for 改成 htmlFor</label>
</li> </ol> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class WorkaroundReactComponent extends React.Component {
  render() {
    return (
      <ol>
        <li className="workaround-class">被迫把 class 改成 className</li>
        <li style={{ backgroundColor: "red" }}>被迫把样式表改成 JSON</li>
        <li>
          <input type="checkbox" id="workaround-for"/>
          <label htmlFor="workaround-for">被迫把 for 改成 htmlFor</label>
        </li>
      </ol>
    );
  }
}

这种开发方式下,前端工程师虽然可以把HTML原型复制粘贴到代码中,但还需要大量改造才能实际运行。比Cycle.js、Widok或者ScalaTags省不了太多事。

不兼容原生DOM操作

此外,ReactJS等一些前端框架,会生成虚拟DOM。虚拟DOM无法兼容浏览器原生的DOM
API
,导致和jQuery、D3等其他库协作时困难重重。比如ReactJS更新DOM对象时常常会破坏掉jQuery控件。

Reddit很多人讨论了这个问题。他们没有办法,只能弃用jQuery。我司的某客户在用了ReactJS后也被迫用ReactJS重写了大量jQeury控件。

Binding.scala中的XHTML

现在有了Binding.scala ,可以在@dom方法中,直接编写XHTML。比如:

JavaScript

@dom def introductionDiv = { <div style=”font-size:0.8em”>
<h3>Binding.scala的优点</h3> <ul>
<li>简单</li> <li>概念少<br/>功能多</li>
</ul> </div> }

1
2
3
4
5
6
7
8
9
@dom def introductionDiv = {
  <div style="font-size:0.8em">
    <h3>Binding.scala的优点</h3>
    <ul>
      <li>简单</li>
      <li>概念少<br/>功能多</li>
    </ul>
  </div>
}

以上代码会被编译,直接创建真实的DOM对象,而没有虚拟DOM。

Binding.scala对浏览器原生DOM的支持很好,你可以在这些DOM对象上调用DOM
API,与 D3、jQuery等其他库交互也完全没有问题。

ReactJS对XHTML语法的残缺不全。相比之下,Binding.scala支持完整的XHTML语法,前端工程师可以直接把设计好的HTML原型复制粘贴到代码中,整个网站就可以运行了。

Binding.scala中XHTML的类型

@dom方法中XHTML对象的类型是Node的派生类。

比如,<div></div>
的类型就是HTMLDivElement,而
<button></button> 的类型就是
HTMLButtonElement。

此外, @dom
注解会修改整个方法的返回值,包装成一个Binding。

JavaScript

@dom def typedButton: Binding[HTMLButtonElement] = {
<button>按钮</button> }

1
2
3
@dom def typedButton: Binding[HTMLButtonElement] = {
  <button>按钮</button>
}

注意typedButton是个原生的HTMLButtonElement,所以可以直接对它调用 DOM
API。比如:

JavaScript

@dom val autoPrintln: Binding[Unit] = {
println(typedButton.bind.innerHTML) // 在控制台中打印按钮内部的 HTML }
autoPrintln.watch()

1
2
3
4
@dom val autoPrintln: Binding[Unit] = {
  println(typedButton.bind.innerHTML) // 在控制台中打印按钮内部的 HTML
}
autoPrintln.watch()

这段代码中,typedButton.bind.innerHTML 调用了 DOM API
HTMLButtonElement.innerHTML。通过autoPrintln.watch(),每当按钮发生更新,autoPrintln中的代码就会执行一次。

其他HTML节点

Binding.scala支持HTML注释:

JavaScript

@dom def comment = { <!– 你看不见我 –> }

1
2
3
@dom def comment = {
  <!– 你看不见我 –>
}

Binding.scala也支持CDATA块:

JavaScript

@dom def inlineStyle = { <section> <style><![CDATA[
.highlight { background-color:gold } ]]></style> <p
class=”highlight”>Binding.scala真好用!</p> </section> }

1
2
3
4
5
6
7
8
9
10
@dom def inlineStyle = {
  <section>
    <style><![CDATA[
      .highlight {
        background-color:gold
      }
    ]]></style>
    <p class="highlight">Binding.scala真好用!</p>
  </section>
}

内嵌Scala代码

除了可以把XHTML内嵌在Scala代码中的 @dom 方法中,Binding.scala 还支持用
{ ... } 语法把 Scala 代码内嵌到XHTML中。比如:

JavaScript

@dom def randomParagraph = { <p>生成一个随机数: {
math.random.toString }</p> }

1
2
3
@dom def randomParagraph = {
  <p>生成一个随机数: { math.random.toString }</p>
}

XHTML中内嵌的Scala代码可以用 .bind 绑定变量或者调用其他 @dom
方法,比如:

JavaScript

val now = Var(new Date) window.setInterval(1000) { now := new Date }
@dom def render = { <div> 现在时间:{ now.bind.toString } {
introductionDiv.bind } { inlineStyle.bind } { typedButton.bind } {
comment.bind } { randomParagraph.bind } </div> }

1
2
3
4
5
6
7
8
9
10
11
12
13
val now = Var(new Date)
window.setInterval(1000) { now := new Date }
 
@dom def render = {
  <div>
    现在时间:{ now.bind.toString }
    { introductionDiv.bind }
    { inlineStyle.bind }
    { typedButton.bind }
    { comment.bind }
    { randomParagraph.bind }
  </div>
}

上述代码渲染出的网页中,时间会动态改变。

强类型的 XHTML

Binding.scala中的XHTML 都支持静态类型检查。比如:

JavaScript

@dom def typo = { val myDiv = <div
typoProperty=”xx”>content</div> myDiv.typoMethod() myDiv }

1
2
3
4
5
@dom def typo = {
  val myDiv = <div typoProperty="xx">content</div>
  myDiv.typoMethod()
  myDiv
}

由于以上代码有拼写错误,编译器就会报错:

JavaScript

typo.scala:23: value typoProperty is not a member of
org.scalajs.dom.html.Div val myDiv = <div
typoProperty=”xx”>content</div> ^ typo.scala:24: value
typoMethod is not a member of org.scalajs.dom.html.Div
myDiv.typoMethod() ^

1
2
3
4
5
6
typo.scala:23: value typoProperty is not a member of org.scalajs.dom.html.Div
        val myDiv = <div typoProperty="xx">content</div>
                     ^
typo.scala:24: value typoMethod is not a member of org.scalajs.dom.html.Div
        myDiv.typoMethod()
              ^

内联CSS属性

style 属性设置内联样式时,style 的值是个字符串。比如:

JavaScript

@dom def invalidInlineStyle = { <div style=”color: blue;
typoStyleName: typoStyleValue”></div> }

1
2
3
@dom def invalidInlineStyle = {
  <div style="color: blue; typoStyleName: typoStyleValue"></div>
}

以上代码中设置的 typoStyleName 样式名写错了,但编译器并没有报错。

要想让编译器能检查内联样式,可以用 style: 前缀而不用 style
属性。比如:

JavaScript

@dom def invalidInlineStyle = { <div style:color=”blue”
style:typoStyleName=”typoStyleValue”></div> }

1
2
3
@dom def invalidInlineStyle = {
  <div style:color="blue" style:typoStyleName="typoStyleValue"></div>
}

那么编译器就会报错:

JavaScript

typo.scala:28: value typoStyleName is not a member of
org.scalajs.dom.raw.CSSStyleDeclaration <div style:color=”blue”
style:typoStyleName=”typoStyleValue”></div> ^

1
2
3
typo.scala:28: value typoStyleName is not a member of org.scalajs.dom.raw.CSSStyleDeclaration
        <div style:color="blue" style:typoStyleName="typoStyleValue"></div>
         ^

这样一来,可以在编写代码时就知道属性有没有写对。不像原生JavaScript /
HTML / CSS那样,遇到bug也查不出来。

自定义属性

如果你需要绕开对属性的类型检查,以便为HTML元素添加定制数据,你可以属性加上
data: 前缀,比如:

JavaScript

@dom def myCustomDiv = { <div
data:customAttributeName=”attributeValue”></div> }

1
2
3
@dom def myCustomDiv = {
  <div data:customAttributeName="attributeValue"></div>
}

这样一来Scala编译器就不会报错了。

结论

本文的完整DEMO请访问
ScalaFiddle。

从这些示例可以看出,Binding.scala 一方面支持完整的XHTML
,可以从高保真HTML
原型无缝移植到动态网页中,开发过程极为顺畅。另一方面,Binding.scala
可以在编译时静态检查XHTML中出现语法错误和语义错误,从而避免bug 。

以下表格对比了ReactJS和Binding.scala对HTML语法的支持程度:

ReactJS Binding.scala
是否支持HTML语法? 残缺支持
是否支持标准的style属性? 不支持,必须改用 JSON 语法
是否支持标准的class属性? 不支持,必须改用className
是否支持标准的for属性? 不支持,必须改用htmlFor
是否支持HTML注释? 不支持
是否兼容原生DOM操作? 不兼容
是否兼容jQuery? 不兼容
能否在编译时检查出错误? 不能

我将在下一篇文章中介绍 Binding.scala
如何实现服务器发送请求并在页面显示结果的流程。

相关链接

  • Binding.scala
    项目主页
  • Binding.scala • TodoMVC
    项目主页
  • Binding.scala • TodoMVC
    DEMO
  • Binding.scala • TodoMVC 以外的其他
    DEMO
  • JavaScript 到 Scala.js
    移植指南
  • Scala.js 项目主页
  • Scala API
    参考文档
  • Scala.js API
    参考文档
  • Scala.js DOM API
    参考文档
  • Binding.scala快速上手指南
  • Binding.scala
    API参考文档
  • Binding.scala 的 Gitter
    聊天室

    1 赞 1 收藏 1
    评论

关于作者:ThoughtWorks

威尼斯使命 1

ThoughtWorks是一家全球IT咨询公司,追求卓越软件质量,致力于科技驱动商业变革。擅长构建定制化软件产品,帮助客户快速将概念转化为价值。同时为客户提供用户体验设计、技术战略咨询、组织转型等咨询服务。

个人主页 ·
我的文章 ·
84 ·
  

威尼斯使命 2

发表评论

电子邮件地址不会被公开。 必填项已用*标注