来 DTDS 全球数字人才发展峰会,与刘润、叶军、快刀青衣畅聊成长>> 了解详情
写点什么

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

2016 年 11 月 22 日

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

一、其他前端框架的问题

对 HTML 的残缺支持

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

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

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

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

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

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

这种开发方式下,前端工程师虽然可以把 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。比如:

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

以上代码会被编译,直接创建真实的 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

复制代码
@dom def typedButton: Binding[HTMLButtonElement] = {
<span><span><<span>button</span>></span> 按钮 <span></<span>button</span>></span>
}</span>

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

复制代码
@dom val autoPrintln: Binding[Unit] = {
println(typedButton.bind.innerHTML)
}
autoPrintln.watch()

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

其他 HTML 节点

Binding.scala 支持 HTML 注释:

复制代码
@dom def comment = {
<span>
}</span>

Binding.scala 也支持 CDATA 块:

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

内嵌 Scala 代码

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

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

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

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

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

强类型的 XHTML

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

复制代码
@dom def typo = {
val myDiv = <span><span><<span>div</span> <span>typoProperty</span>=<span>"xx"</span>></span>content<span></<span>div</span>></span>
myDiv.typoMethod()
myDiv
}</span>

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

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

内联 CSS 属性

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

复制代码
@dom def invalidInlineStyle = {
<span><span><<span>div</span> <span>style</span>=<span>"color: blue; typoStyleName: typoStyleValue"</span>></span><span></<span>div</span>></span>
}</span>

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

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

复制代码
@dom def invalidInlineStyle = {
<span><span><<span>div</span> <span>style:color</span>=<span>"blue"</span> <span>style:typoStyleName</span>=<span>"typoStyleValue"</span>></span><span></<span>div</span>></span>
}</span>

那么编译器就会报错:

复制代码
typo.scala:<span>28</span>: value typoStyleName is not a member of org.scalajs.dom.raw.CSSStyleDeclaration
<div style:color=<span>"blue"</span> style:typoStyleName=<span>"typoStyleValue"</span>><span><span></<span>div</span>></span>
^</span>

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

自定义属性

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

复制代码
@dom def myCustomDiv = {
<span><span><<span>div</span> <span>data:customAttributeName</span>=<span>"attributeValue"</span>></span><span></<span>div</span>></span>
}</span>

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

三、结论

本文的完整 DEMO 请访问 ScalaFiddle

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

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

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

四、相关链接

五、More than React 系列文章

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

《More than React(二)组件对复用性有害?》

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

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

《More than React(五)异步编程真的好吗?》

作者简介

杨博是 Haxe 和 Scala 社区的活跃贡献者,发起和维护的开源项目包括 protoc-gen-as3 Stateless Future haxe-continuation Fastring Each Microbuilder Binding.scala 。杨博曾在网易任主程序和项目经理,开发过多款游戏。现在 ThoughtWorks 任 Lead Consultant,为客户提供移动、互联网、大数据、人工智能和深度学习领域的解决方案。


感谢张凯峰对本文的策划,韩婷对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 11 月 22 日 02:004034

评论

发布
暂无评论
发现更多内容

SAPGUI里实现自定义的语法检查

Jerry Wang

SAP abap SAPGUI 语法检查

如何在ubuntu上安装virtualbox的driver module vboxdrv

Jerry Wang

ubuntu 虚拟机 vboxdrv 驱动

S/4HANA for Customer Management里的搜索分页处理

Jerry Wang

CRM SAP abap S/4HANA

S4CRM和C4C的技术比较

Jerry Wang

CRM SAP ERP abap Cloud for Customer

高性能 JavaScriptの六 -- 老生常谈Ajax

空城机

JavaScript ajax 前端 5月日更

如何证明CRM WebClient UI上的应用是有状态(Stateful)的

Jerry Wang

CRM SAP abap WebClient UI

如何使用SAP Cloud for Customer里的ABSL代码调用Web service

Jerry Wang

SAP C4C Cloud for Customer

会说话的ABAP report

Jerry Wang

SAP abap SAPGUI

S4HANA和CRM Fiori应用的搜索分页实现

Jerry Wang

CRM SAP Fiori SAP UI5 S/4HANA

ABAP和Java里关于DEFAULT(默认)机制的一些语言特性

Jerry Wang

SAP abap Netweaver SAPGUI

ABAP的语法高亮是如何在浏览器里显示的

Jerry Wang

SAP abap SAPGUI 语法高亮

优化docker镜像的几种方法

运维研习社

Docker 镜像 优化技巧 5月日更

人生算法:做事要闭环

石云升

读书笔记 职场经验 5月日更 人生算法

CRM订单状态的Open, In process和Completed这些条目是从哪里来的

Jerry Wang

CRM SAP ERP abap

在浏览器里使用SAPGUI里的SE80

Jerry Wang

JavaScript SAP abap Fiori SAP UI5

CRM, C4C和Hybris的工作流简介

Jerry Wang

CRM SAP C4C Hybris Commerce Cloud

sap.ui.require in SAP UI5 and require in nodejs

Jerry Wang

nodejs SAP SAP UI5

如何在Chrome development tool里查看C4C前台发送的未经 GZIP 压缩之前的请求细节

Jerry Wang

chrome SAP C4C Chrome开发者工具

如何使用腾讯云提供的云主机

Jerry Wang

腾讯云 云主机 Cloud

ABAP宏的调试

Jerry Wang

调试 SAP abap macro

SAP UI5和CRM WebUI的View和Controller是如何绑定的

Jerry Wang

CRM SAP abap WebClient UI SAP UI5

SAP UI5, CRM, S/4HANA 和 C4C里的Association, Composition and Aggregation

Jerry Wang

JavaScript CRM SAP SAP UI5

Dubbo 令牌验证和优雅停机

青年IT男

dubbo

架构师训练营 作业四

开拓纪

ABAP git客户端的简单介绍

Jerry Wang

GitHub SAP abap

如何将iso文件安装到Virtual里的ubuntu去

Jerry Wang

Linux ubuntu windows 虚拟机 Windows 10

如何使用Putty登录安装在VirtualBox里的ubuntu

Jerry Wang

ubuntu windows 虚拟机

如何给VirtualBox虚拟机的ubuntu LVM分区扩容

Jerry Wang

虚拟机 Cloud virtualbox CloudFoundry

架构实战营 - 模块四作业

Sun

那些年我用过的SAP IDE

Jerry Wang

ide SAP abap SAPGUI

SAP Fiori里的List是如何做到懒加载Lazy load的

Jerry Wang

JavaScript SAP Fiori SAP UI5

「中国技术开放日·长沙站」现场直播

「中国技术开放日·长沙站」现场直播

More than React(四)HTML也可以静态编译?-InfoQ