HTML也可以静态编译?

作者:信息技术

对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 属性替换成 classNamehtmlFor,还要把内联的 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省不了太多事。

相关链接

  • 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 收藏 评论

为什么 ReactJS 不适合复杂的前端项目?

2016/08/17 · JavaScript · 15 评论 · React, ReactJS, 前端

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

《More than React》系列的文章会一共分为五篇。本文是第一篇,介绍用ReactJS开发时遇到的种种问题。后面四篇文章的每一篇将会分别详细讨论其中一个问题,以及Binding.scala如何解决这个问题。

关于作者:ThoughtWorks

图片 1

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

图片 2

原生 DHTML 版

首先,我试着不用任何前端框架,直接调用原生的 DHTML API 来实现标签编辑器,代码如下:

JavaScript

<!DOCTYPE html> <html> <head> <script> var tags = []; function hasTag(tag) { for (var i = 0; i < tags.length; i++) { if (tags[i].tag == tag) { return true; } } return false; } function removeTag(tag) { for (var i = 0; i < tags.length; i++) { if (tags[i].tag == tag) { document.getElementById("tags-parent").removeChild(tags[i].element); tags.splice(i, 1); return; } } } function addTag(tag) { var element = document.createElement("q"); element.textContent = tag; var removeButton = document.createElement("button"); removeButton.textContent = "x"; removeButton.onclick = function (event) { removeTag(tag); } element.appendChild(removeButton); document.getElementById("tags-parent").appendChild(element); tags.push({ tag: tag, element: element }); } function addHandler() { var tagInput = document.getElementById("tag-input"); var tag = tagInput.value; if (tag && !hasTag(tag)) { addTag(tag); tagInput.value = ""; } } </script> </head> <body> <div id="tags-parent"></div> <div> <input id="tag-input" type="text"/> <button onclick="addHandler()">Add</button> </div> <script> addTag("initial-tag-1"); addTag("initial-tag-2"); </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;script&gt;
    var tags = [];
 
    function hasTag(tag) {
      for (var i = 0; i &lt; tags.length; i++) {
        if (tags[i].tag == tag) {
          return true;
        }
      }
      return false;
    }
 
    function removeTag(tag) {
      for (var i = 0; i &lt; tags.length; i++) {
        if (tags[i].tag == tag) {
          document.getElementById("tags-parent").removeChild(tags[i].element);
          tags.splice(i, 1);
          return;
        }
      }
    }
 
    function addTag(tag) {
      var element = document.createElement("q");
      element.textContent = tag;
      var removeButton = document.createElement("button");
      removeButton.textContent = "x";
      removeButton.onclick = function (event) {
        removeTag(tag);
      }
      element.appendChild(removeButton);
      document.getElementById("tags-parent").appendChild(element);
      tags.push({
        tag: tag,
        element: element
      });
    }
 
    function addHandler() {
      var tagInput = document.getElementById("tag-input");
      var tag = tagInput.value;
      if (tag &amp;&amp; !hasTag(tag)) {
        addTag(tag);
        tagInput.value = "";
      }
    }
  &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="tags-parent"&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;input id="tag-input" type="text"/&gt;
    &lt;button onclick="addHandler()"&gt;Add&lt;/button&gt;
  &lt;/div&gt;
  &lt;script&gt;
    addTag("initial-tag-1");
    addTag("initial-tag-2");
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
 

为了实现标签编辑器的功能,我用了 45 行 JavaScript 代码来编写 UI 逻辑,外加若干的 HTML <div> 外加两行 JavaScript 代码填入初始化数据。

HTML 文件中硬编码了几个 <div>。这些<div> 本身并不是动态创建的,但可以作为容器,放置其他动态创建的元素。

代码中的函数来会把网页内容动态更新到这些 <div> 中。所以,如果要在同一个页面显示两个标签编辑器,id 就会冲突。因此,以上代码没有复用性。

就算用 jQuery 代替 DHTML API,代码复用仍然很难。为了复用 UI ,jQuery 开发者通常必须额外增加代码,在 onload 时扫描整个网页,找出具有特定 class 属性的元素,然后对这些元素进行修改。对于复杂的网页,这些 onload 时运行的函数很容易就会冲突,比如一个函数修改了一个 HTML 元素,常常导致另一处代码受影响而内部状态错乱。

问题二:ReactJS的虚拟DOM 算法又慢又不准

ReactJS的页面渲染算法是虚拟DOM差量算法。

开发者需要提供 render 函数,根据 propsstate 生成虚拟 DOM。 然后 ReactJS 框架根据 render 返回的虚拟 DOM 创建相同结构的真实 DOM.

每当 state 更改时,ReacJS 框架重新调用 render 函数,获取新的虚拟 DOM 。 然后,框架会比较上次生成的虚拟 DOM 和新的虚拟 DOM 有哪些差异,然后把差异应用到真实DOM上。

这样做有两大缺点:

  1. 每次 state 更改,render 函数都要生成完整的虚拟 DOM. 哪怕 state 改动很小,render函数也会完整计算一遍。如果 render 函数很复杂,这个过程就白白浪费了很多计算资源。
  2. ReactJS框架比较虚拟DOM差异的过程,既慢又容易出错。比如,假如你想要在某个 <ul>列表的顶部插入一项 <li> ,那么ReactJS框架会误以为你修改了 <ul> 的每一项 <li>,然后在尾部插入了一个 <li>

这是因为 ReactJS收到的新旧两个虚拟DOM之间相互独立,ReactJS并不知道数据源发生了什么操作,只能根据新旧两个虚拟DOM来猜测需要执行的操作。 自动的猜测算法既不准又慢,必须要前端开发者手动提供 key 属性、shouldComponentUpdate 方法、componentDidUpdate 方法或者 componentWillUpdate 等方法才能帮助 ReactJS 框架猜对。

我将在《More than React(三)虚拟DOM已死?》中比较ReactJS、AngularJS和Binding.scala渲染机制,介绍简单性能高的Binding.scala精确数据绑定机制。

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中的代码就会执行一次。

标签编辑器的功能需求

在InfoQ的许多文章都有标签。比如本文的标签是“binding.scala”、“data-binding”、“scala.js”。

假如你要开发一个博客系统,你也希望博客作者可以添加标签。所以你可能会提供标签编辑器供博客作者使用。

如图所示,标签编辑器在视觉上分为两行。

图片 3

第一行展示已经添加的所有标签,每个标签旁边有个“x”按钮可以删除标签。第二行是一个文本框和一个“Add”按钮可以把文本框的内容添加为新标签。每次点击“Add”按钮时,标签编辑器应该检查标签是否已经添加过,以免重复添加标签。而在成功添加标签后,还应清空文本框,以便用户输入新的标签。

除了用户界面以外,标签编辑器还应该提供 API 。标签编辑器所在的页面可以用 API 填入初始标签,也可以调用 API 随时增删查改标签。如果用户增删了标签,应该有某种机制通知页面的其他部分。

关于作者:ThoughtWorks

图片 4

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

图片 5

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 ReactJS Binding.scala
实现标签编辑器需要代码行数 45行 51行 17行
实现标签编辑器的难点 在代码中动态更新HTML页面太繁琐 实现组件的语法很笨重
使用标签编辑器并显示标签列表需要代码行数 难以复用 21行 8行
阻碍复用的难点 静态HTML元素难以模块化 交互组件之间层层传递回调函数过于复杂

Binding.scala 不发明“组件”之类的噱头,而以更轻巧的“方法”为最小复用单位,让编程体验更加顺畅,获得了更好的代码复用性。

本系列下一篇文章将比较 ReactJS 的虚拟 DOM 机制和 Binding.scala 的精确数据绑定机制,揭开 ReactJS 和 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 赞 5 收藏 15 评论

相关链接

  • 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 评论

Binding.scala 的基本用法

在讲解 Binding.scala 如何实现标签编辑器以前,我先介绍一些 Binding.scala 的基础知识:

Binding.scala 中的最小复用单位是数据绑定表达式,即 @dom 方法。每个 @dom 方法是一段 HTML 模板。比如:

JavaScript

// 两个 HTML 换行符 @dom def twoBr = <br/><br/>

1
2
3
// 两个 HTML 换行符
@dom def twoBr = &lt;br/&gt;&lt;br/&gt;
 

JavaScript

// 一个 HTML 标题 @dom def myHeading(content: String) = <h1>{content}</h1>

1
2
3
// 一个 HTML 标题
@dom def myHeading(content: String) = &lt;h1&gt;{content}&lt;/h1&gt;
 

每个模板还可以使用bind语法包含其他子模板,比如:

JavaScript

@dom def render = { <div> { myHeading("Binding.scala的特点").bind } <p> 代码短 { twoBr.bind } 概念少 { twoBr.bind } 功能多 </p> </div> }

1
2
3
4
5
6
7
8
9
10
11
12
13
@dom def render = {
  &lt;div&gt;
    { myHeading("Binding.scala的特点").bind }
    &lt;p&gt;
      代码短
      { twoBr.bind }
      概念少
      { twoBr.bind }
      功能多
    &lt;/p&gt;
  &lt;/div&gt;
}
 

你可以参见附录:Binding.scala快速上手指南,学习上手Binding.scala开发的具体步骤。

此外,本系列第四篇文章《HTML也可以编译》还将列出Binding.scala所支持的完整HTML模板特性。

背景介绍

去年 4 月,我第一次在某个客户的项目中接触到ReactJS 。

我发现ReactJS要比我以前用过的AngularJS简单很多,它提供了响应式的数据绑定功能,把数据映射到网页上,使我可以轻松实现交互简单的网站。

然而,随着我越来越深入的使用ReactJS,我发现用ReactJS编写交互复杂的网页很困难。 我希望有一种方式,能够像ReactJS一样简单解决简单问题。此外,还要能简单解决复杂问题。

于是我把ReactJS用Scala重新写了一个。代码量从近三万行降到了一千多行。

用这个框架实现的TodoMVC应用,只用了154行代码。而用ReactJS实现相同功能的TodoMVC,需要488行代码。

下图是用Binding.scala实现的TodoMVC应用。

图片 6

这个框架就是Binding.scala。

本文由杏彩发布,转载请注明来源

关键词: