写点什么

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

  • 2016-11-22
  • 本文字数:4602 字

    阅读完需:约 15 分钟

《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:007080

评论

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

腾讯T4架构师:刷3遍以下面试题,你也能从小公司成功跳到大厂

Java架构之路

Java 程序员 架构 面试 编程语言

远见而明察近观若明火|Centos7.6环境基于Prometheus和Grafana结合钉钉机器人打造全时监控(预警)Docker容器服务系统

刘悦的技术博客

Docker 高可用 监控 Prometheus 预警

LeetCode题解:92. 反转链表 II,迭代,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

数据为墨,智能作笔:画一卷新姑苏繁华图

脑极体

业务中台建设 - 配置化

孝鹏

中台 微服务 配置化开发

规模化敏捷框架何从入手?这篇文章把SAFe讲透了!

华为云开发者联盟

敏捷开发 框架 safe

Java 细粒度锁续篇

rookiedev

Java 多线程 加锁

区块链农场游戏系统开发软件定制

JVM 的运行时数据区域分布

rookiedev

Java JVM

区块链矿机挖矿系统开发软件技术

SQL优化最干货总结-MySQL「2020年终总结版」

Java架构师迁哥

wildfly 21的domain配置

程序那些事

程序那些事 wildfly wildfly21 配置管理 domain模式

FastAI:滴普技术荟:基于机器视觉的典型多目标追踪算法应用实践

目标追踪 目标检测 追踪算法

低代码与零代码工具的这些特征,弥补了所有人和IT之间的差距!

J2PaaS低代码平台

程序员 互联网 开发者 软件开发 开发工具

彩色的线,数据的诗,你好——贵州鲲鹏!

脑极体

“社恐”独处好去处:无人自习室,一个人的“世外桃源”

IoT云工坊

物联网 无人自习室 智能门禁 智能灯控 线上预约

[git使用技巧] git提交忽略不必要的文件或文件夹

xcbeyond

git 七日更

Nginx常见典型故障|Linux干货

赖猫

c++ nginx Linux

关于昆明市政协、市统战部、民革昆明市委赴云南坤艮盈科技有限公司(商务部CECBC区块链专委会秘书处云南办事处)调研指导工作

CECBC

云南发展

比特币的安全性到底有多高?

CECBC

比特币

假冒、诈骗、隐私安全,如何应对数字人民币的风险与挑战?

CECBC

货币

如何成为架构师?

xcbeyond

个人成长 架构师 七日更

TypeScript | 第三章:函数、泛型和枚举

梁龙先森

typescript 编程 大前端 七日更

让你的简历不落窠臼,精雕细镂写一份真正的技术简历(Python向)

刘悦的技术博客

Python 面试 简历优化 简历

转型项目经理?

escray

面试 面经 七日更 十日谈

FastAI:滴普技术荟:基于深度学习的云边一体化OLED屏缺陷自动光学检测技术

学习 缺陷检测 云边一体 自动光学检测

职业规划

Albert

职业规划 七日更

用大白话给你解释Zookeeper的选举机制

爱笑的架构师

zookeeper ZooKeeper原理 七日更

Ribbon使用及其内核原理剖析

Fox爱分享

使用 Helmfile 解放你的 Helm Chart

郭旭东

云原生 Helm

FastAI:滴普技术荟:某工业产品内部结构尺寸图像测量和缺陷检测分析

AI 目标检测 图像处理 缺陷检测 图像检测

More than React(四)HTML也可以静态编译?_JavaScript_杨博_InfoQ精选文章