写点什么

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:006784

评论

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

JD商品详情API:京东电商数据整合的关键一环

技术冰糖葫芦

API 接口 API 测试

React Native 应用打包上架

定制化区块链交易所开发:Dapp、DeFi和IDO的全方位解决方案

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

用three.js做一个3D汉诺塔游戏(上)

OpenTiny社区

JavaScript 前端 Web OpenTiny

助力全球企业和开发者们应对多方网络挑战,腾讯云EdgeOne已成企业高质量出海“首选”

极客天地

谈谈Node.js版本管理工具

伤感汤姆布利柏

Solidity案例详解(七)供应链金融合约

BSN研习社

区块链 Solidity

利用Airtest技术实现基于图像识别的自动化测试

测吧(北京)科技有限公司

测试

利用Allure与截图技术生成详尽测试报告

测吧(北京)科技有限公司

测试

采用PO设计模式编写自动化测试用例

测吧(北京)科技有限公司

测试

西安交易所开发:打造区块链交易系统的DApp开发

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

网站性能优化最佳实践--如何减少文件体积

观测云

性能优化

解决App自动化测试中的弹窗问题:常见解决方案

测吧(北京)科技有限公司

测试

ATX技术应用:了解并掌握ATX技术实现自动化测试

测吧(北京)科技有限公司

测试

Web3.0热门领域NFT项目实战数字平台艺术

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

论低代码开与AI时代的适配性

不在线第一只蜗牛

人工智能 AI 低代码

DevOps与低代码

Jianmu

微调工程师岗位可能并不存在,但使用 AI 编码工具已经成为刚需

阿里巴巴云原生

阿里云 云原生

Flink 中 Task(任务)的概念、定位及应用详解与易混淆点梳理

木南曌

flink 实时计算

面对API的安全风险,WAAP全站防护的作用

德迅云安全杨德俊

采用Page Object(PO)设计模式编写自动化测试用例

测吧(北京)科技有限公司

测试

阐述区块链“链游”项目3D/2D模式系统开发

区块链软件开发推广运营

区块链游戏 dapp开发 链游开发 NFT开发 公链开发

DevOps与低代码

都广科技

DevOps

新质生产力与零信任数据安全:携手共创未来

从云科技

数据安全 零信任 新质生产力

软件测试学习笔记丨Allure2报告中添加用例优先级

测试人

软件测试

探索云原生时代:技术驱动的业务架构革新

不在线第一只蜗牛

云计算 架构 云原生

Knative 助力 XTransfer 加速应用云原生 Serverless 化

阿里巴巴云原生

阿里云 云原生 Knative

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