HTML也得以静态编译,损害了复用性

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语法。

React.Component 损害了复用性?

HTML也得以静态编译,损害了复用性。2016/09/07 · 基础技术 ·
binding.scala,
data-binding,
React,
scala.js

本文小编: 伯乐在线 –
ThoughtWorks
。未经作者许可,禁止转发!
迎接参预伯乐在线 专辑作者。

本种类的上一篇小说《为什么ReactJS不适合复杂的前端项目》列举了前端开发中的各个痛点。本篇小说中校详细探索其中“复用性”痛点。我们将用原生
DHTML API 、 ReactJS 和 Binding.scala
完成同一个急需复用的标签编辑器,然后比较五个标签编辑器哪个落成难度更低,哪个更好用。

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

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

正文笔者: 伯乐在线 –
ThoughtWorks
。未经小编许可,禁止转发!
迎接插足伯乐在线 专辑作者。

《More than
React》种类的小说会一起分成五篇。本文是率先篇,介绍用ReactJS开发时遇上的各种难点。后边四篇小说的每一篇将会分别详细座谈之中一个题目,以及Binding.scala如何缓解那个难点。

虚拟 DOM 已死?

2016/10/24 · 基础技术 ·
1 评论 ·
DOM

本文作者: 伯乐在线 –
ThoughtWorks
。未经小编许可,禁止转发!
欢迎出席伯乐在线 专栏撰稿人。

本种类小说:

  • 《怎么 ReactJS
    不合乎复杂的前端项目?》
  • 《React.Component
    损害了复用性?》

本体系的上一篇作品《React.Component
损害了复用性?》研讨了什么样在前端开发中编辑可复用的界面元素。本篇文章将从质量和算法的角度比较Binding.scala 和其余框架的渲染机制。

Binding.scala 完毕了一套精确数据绑定机制,通过在模板中利用 bind
for/yield 来渲染页面。你可能用过部分任何 Web
框架,大多选取脏检查或者虚拟 DOM 机制。和它们相比较,Binding.scala
的标准数据绑定机制使用更简明、代码更硬朗、品质更高。

任何前端框架的题材

标签编辑器的听从须求

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

假设你要支付一个博客系统,你也期望博客作者可以增加标签。所以你也许会提供标签编辑器供博客小编利用。

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

澳门葡京 1

首先行浮现已经添加的富有标签,每个标签旁边有个“x”按钮可以去除标签。第二行是一个文本框和一个“Add”按钮可以把文本框的始末丰盛为新标签。每一遍点击“Add”按钮时,标签编辑器应该检查标签是或不是业已添加过,避防又一次添加标签。而在中标添加标签后,还应清空文本框,以便用户输入新的标签。

除开用户界面以外,标签编辑器还应有提供 API 。标签编辑器所在的页面可以用
API 填入开始标签,也足以调用 API
随时增删查改标签。如若用户增删了标签,应该有某种机制布告页面的任何一些。

背景介绍

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

本身发觉ReactJS要比自己在此此前用过的AngularJS不难很多,它提供了响应式的多寡绑定作用,把数据映射到网页上,使自己可以轻松落成互相之间不难的网站。

不过,随着我越来越深入的行使ReactJS,我发觉用ReactJS编写交互复杂的网页很窘迫。
我盼望有一种方法,可以像ReactJS一样简单解决简单难题。除此以外,还要能简单解决复杂难题。

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

用那些框架落成的TodoMVC应用,只用了154行代码。而用ReactJS完结平等效果的TodoMVC,需要488行代码。

下图是用Binding.scala达成的TodoMVC应用。

澳门葡京 2

这一个框架就是Binding.scala。

ReactJS虚拟DOM的缺点

例如, ReactJS 使用虚拟 DOM 机制,让前者开发者为每个组件提供一个
render 函数。render 函数把 propsstate 转换成 ReactJS 的虚拟
DOM,然后 ReactJS 框架根据render 重回的虚构 DOM 成立相同结构的实在
DOM。

每当 state 更改时,ReactJS 框架重新调用 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 框架猜对。

对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省持续太多事。

原生 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组件难以在千丝万缕交互页面中复用

ReactJS中的最小复用单位是组件。ReactJS的零部件比AngularJS的Controller和View
要轻量些。 每个组件只必要前端开发者提供一个 render 函数,把 props
state 映射成网页元素。

这么的轻量级组件在渲染简单静态页面时很好用,
可是一旦页面有相互,就必须在组件间传递回调函数来处负责人件。

自身将在《More than React(二)组件对复用性有害?》中用原生DHTML
API、ReactJS和Binding.scala达成同一个亟待复用的页面,介绍Binding.scala怎样简单已毕、不难复用复杂的并行逻辑。

AngularJS的脏检查

而外类似 ReactJS 的杜撰 DOM 机制,其余流行的框架,比如 AngularJS
还会利用脏检查算法来渲染页面。

接近 AngularJS 的脏检查算法和 ReactJS
有一致的毛病,不可能得知景况修改的意向,必须完全重新总结 View
模板。除此之外,AngularJS 更新 DOM
的限制往往会比实际所需大得多,所以会比 ReactJS 还要慢。

不般配原生DOM操作

其它,ReactJS等部分前端框架,会生成虚拟DOM。虚拟DOM不可能合营浏览器原生的DOM
API
,导致和jQuery、D3等其余库合营时困难重重。比如ReactJS更新DOM对象时平时会损坏掉jQuery控件。

Reddit众五个人谈论了这一个难题。他们没有主意,只能够弃用jQuery。我司的某客户在用了ReactJS后也被迫用ReactJS重写了大批量jQeury控件。

ReactJS 达成的竹签编辑器组件

ReactJS 提供了足以复用的零件,即 React.Component 。借使用 ReactJS
已毕标签编辑器,几乎能够如此写:

JavaScript

class TagPicker extends React.Component { static defaultProps = {
changeHandler: tags => {} } static propTypes = { tags:
React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
changeHandler: React.PropTypes.func } state = { tags: this.props.tags }
addHandler = event => { const tag = this.refs.input.value; if (tag
&& this.state.tags.indexOf(tag) == -1) { this.refs.input.value =
“”; const newTags = this.state.tags.concat(tag); this.setState({ tags:
newTags }); this.props.changeHandler(newTags); } } render() { return (
<section> <div>{ this.state.tags.map(tag => <q key={
tag }> { tag } <button onClick={ event => { const newTags =
this.state.tags.filter(t => t != tag); this.setState({ tags: newTags
}); this.props.changeHandler(newTags); }}>x</button> </q>
) }</div> <div> <input type=”text” ref=”input”/>
<button onClick={ this.addHandler }>Add</button>
</div> </section> ); } }

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
class TagPicker extends React.Component {
 
  static defaultProps = {
    changeHandler: tags =&gt; {}
  }
 
  static propTypes = {
    tags: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
    changeHandler: React.PropTypes.func
  }
 
  state = {
    tags: this.props.tags
  }
 
  addHandler = event =&gt; {
    const tag = this.refs.input.value;
    if (tag &amp;&amp; this.state.tags.indexOf(tag) == -1) {
      this.refs.input.value = "";
      const newTags = this.state.tags.concat(tag);
      this.setState({
        tags: newTags
      });
      this.props.changeHandler(newTags);
    }
  }
 
  render() {
    return (
      &lt;section&gt;
        &lt;div&gt;{
          this.state.tags.map(tag =&gt;
            &lt;q key={ tag }&gt;
              { tag }
              &lt;button onClick={ event =&gt; {
                const newTags = this.state.tags.filter(t =&gt; t != tag);
                this.setState({ tags: newTags });
                this.props.changeHandler(newTags);
              }}&gt;x&lt;/button&gt;
            &lt;/q&gt;
          )
        }&lt;/div&gt;
        &lt;div&gt;
          &lt;input type="text" ref="input"/&gt;
          &lt;button onClick={ this.addHandler }&gt;Add&lt;/button&gt;
        &lt;/div&gt;
      &lt;/section&gt;
    );
  }
 
}
 

如上 51 行 ECMAScript 2015
代码达成了一个标签编辑器组件,即TagPicker。就算代码量比 DHTML
版长了一点点,但复用性大大进步了。

倘诺你绝不 ECMAScript 2015 的话,那么代码还会长一些,而且亟需处理部分
JavaScript 的坑,比如在回调函数中用持续 this

ReactJS 开发者可以随时用 ReactDOM.render 函数把 TagPicker
渲染到其余空白元素内。其它,ReactJS 框架可以在 stateprops
改变时触发 render ,从而幸免了手动修改现存的 DOM。

只要不考虑冗余的 key 属性,单个组件内的互相 ReactJS
还算不尽如人意。可是,复杂的网页结构往往需求几个零部件层层嵌套,那种父子组件之间的相互,ReactJS
就很讨厌了。

譬如说,若是需求在 TagPicker
之外显示所有的竹签,每当用户增删标签,这一个标签也要自动更新。要贯彻这几个效果,需求给
TagPicker 传入 changeHandler 回调函数,代码如下:

JavaScript

class Page extends React.Component { state = { tags: [ “initial-tag-1”,
“initial-tag-2” ] }; changeHandler = tags => { this.setState({ tags
}); }; render() { return ( <div> <TagPicker tags={
this.state.tags } changeHandler={ this.changeHandler }/>
<h3>全体标签:</h3> <ol>{ this.state.tags.map(tag
=> <li>{ tag }</li> ) }</ol> </div> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Page extends React.Component {
 
  state = {
    tags: [ "initial-tag-1", "initial-tag-2" ]
  };
 
  changeHandler = tags =&gt; {
    this.setState({ tags });
  };
 
  render() {
    return (
      &lt;div&gt;
        &lt;TagPicker tags={ this.state.tags } changeHandler={ this.changeHandler }/&gt;
        &lt;h3&gt;全部标签:&lt;/h3&gt;
        &lt;ol&gt;{ this.state.tags.map(tag =&gt; &lt;li&gt;{ tag }&lt;/li&gt; ) }&lt;/ol&gt;
      &lt;/div&gt;
    );
  }
 
}
 

为了能接触页面其他部分更新,我被迫扩展了一个 21 行代码的 Page 组件。

Page 组件必须兑现 changeHandler 回调函数。每当回调函数触发,调用
Page 自己的 setState 来触发 Page 重绘。

从那么些例子,大家得以见见, ReactJS
能够概括的化解简单的难题,但碰上层次复杂、交互频仍的网页,完结起来就很麻烦。使用
ReactJS 的前端项目充满了各类 xxxHandler
用来在组件中传递音信。我参与的某国外客户项目,平均每个组件大概须要传入三个回调函数。借使层次嵌套深,创造网页时,平时要求把回调函数从最顶层的零部件一百年不遇传入最底部的机件,而当事件触发时,又必要一稀罕把事件音讯往外传。整个前端项目有大多数代码都在那样绕圈子。

题目二: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的标准数据绑定

Binding.scala 使用规范数据绑定算法来渲染 DOM 。

在 Binding.scala 中,你可以用 @dom 注明注明数据绑定表明式。@dom
会自动把 = 之后的代码包装成 Binding 类型。

比如:

@dom val i: Binding[Int] = 1 @dom def f: Binding[Int] = 100 @dom val
s: Binding[String] = “content”

1
2
3
@dom val i: Binding[Int] = 1
@dom def f: Binding[Int] = 100
@dom val s: Binding[String] = "content"

@dom 既可用以 val 也得以用于 def ,可以表明蕴含 IntString
在内的其余数据类型。

除去,@dom 方法还足以一贯编写 XHTML,比如:

@dom val comment: Binding[Comment] = <!– This is a HTML Comment
–> @dom val br: Binding[HTMLBRElement] = <br/> @dom val seq:
Binding[BindingSeq[HTMLBRElement]] = <br/><br/>

1
2
3
@dom val comment: Binding[Comment] = <!– This is a HTML Comment –>
@dom val br: Binding[HTMLBRElement] = <br/>
@dom val seq: Binding[BindingSeq[HTMLBRElement]] = <br/><br/>

这些 XHTML 生成的
Comment

HTMLBRElement
是 HTML
Node
的派生类。而不是
XMLNode。

每个 @dom 方法都可以凭借其他数据绑定表明式:

val i: Var[Int] = Var(0) @dom val j: Binding[Int] = 2 @dom val k:
Binding[Int] = i.bind * j.bind @dom val div:
Binding[HTMLDivElement] = <div>{ k.bind.toString }</div>

1
2
3
4
val i: Var[Int] = Var(0)
@dom val j: Binding[Int] = 2
@dom val k: Binding[Int] = i.bind * j.bind
@dom val div: Binding[HTMLDivElement] = <div>{ k.bind.toString }</div>

透过那种艺术,你可以编制 XHTML 模板把数据源映射为 XHTML
页面。那种精确的映照关系,描述了数码里面的关系,而不是 ReactJS 的
render
函数那样描述运算进程。所以当数码暴发变动时,只有受影响的局地代码才会重复总结,而不需要重新总结整个
@dom 方法。

比如:

val count = Var(0) @dom def status: Binding[String] = { val start提姆e
= new Date “本页面起先化的光阴是” + start提姆e.toString + “。按钮被按过”

  • count.bind.toString + “次。按钮最终一遍按下的岁月是” + (new
    Date).toString } @dom def render = { <div> { status.bind }
    <button onclick={ event: 伊夫nt => count := count.get + 1
    }>更新意况</button> </div> }
1
2
3
4
5
6
7
8
9
10
11
12
13
val count = Var(0)
 
@dom def status: Binding[String] = {
  val startTime = new Date
  "本页面初始化的时间是" + startTime.toString + "。按钮被按过" + count.bind.toString + "次。按钮最后一次按下的时间是" + (new Date).toString
}
 
@dom def render = {
  <div>
    { status.bind }
    <button onclick={ event: Event => count := count.get + 1 }>更新状态</button>
  </div>
}

上述代码可以在ScalaFiddle骨子里运行一下试跳。

注意,status
并不是一个平日的函数,而是描述变量之间涉及的更加表达式,每一次渲染时只举行其中有些代码。比如,当
count 改变时,只有位于 count.bind 将来的代码才会重复统计。由于
val startTime = new Date 位于 count.bind
之前,并不会重复计算,所以会间接维持为打开网页首次执行时的起初值。

稍许人在念书 ReactJS 或者 AngularJS 时,必要学习 key
shouldComponentUpdate$apply$digest 等繁杂概念。这个概念在
Binding.scala 中一直不存在。因为 Binding.scala 的 @dom
方法描述的是变量之间的关系。所以,Binding.scala
框架知道确切数据绑定关系,可以自动检测出需求更新的微小部分。

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 的主干用法

在助教 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模板特性。

题材三:ReactJS的HTML模板成效既不完备、也不健全

ReactJS支持用JSX编写HTML模板。

理论上,前端工程师只要把静态HTML原型复制到JSX源文件中,
增加部分变量替换代码, 就能改造成动态页面。
理论上那种做法要比Cycle.js、Widok、ScalaTags等框架更适合复用设计师提供的HTML原型。

糟糕的是,ReactJS对HTML的支撑四分五裂。开发者必须手动把classfor特性替换成classNamehtmlFor,还要把内联的style体制从CSS语法改成JSON语法,代码才能运作。
这种开发格局下,前端工程师即便可以把HTML原型复制粘贴到代码中,但还亟需大批量改造才能实际运行。
比Cycle.js、Widok、或者、ScalaTags省四处太多事。

除了,ReactJS还提供了propTypes机制校验虚拟DOM的合法性。
然则,这一体制也漏洞百出。
就算指定了propTypes,ReactJS也无法在编译前提前发现错误。只有测试覆盖率很高的门类时才能在每个组件使用其它零件时展开校验。
就算测试覆盖率很高,propTypes依然不可能检测出拼错的属性名,若是您把onClick写成了onclick
ReactJS就不会报错,往往导致开发者额外成本大批量光阴排查一个很粗略的bug。

自家将在《More than
React(四)HTML也可以编译?》中相比ReactJS和Binding.scala的HTML模板,介绍Binding.scala怎么着在一体化扶助XHTML语法的还要静态检查语法错误和语义错误。

结论

正文相比了虚拟 DOM 、脏检查和标准数据绑定两种渲染机制。

AngularJS ReactJS Binding.scala
渲染机制 脏检查 虚拟DOM 精确数据绑定
数据变更时的运算步骤
  1. 重复检查数据是否更改
  2. 大范围更新页面,哪怕这部分页面根本没有修改
  1. 重新生成整个虚拟DOM
  2. 比较新旧虚拟DOM的差异
  3. 根据差异更新页面
  1. 直接根据数据映射关系,更新最小范围页面
检测页面更新范围的准确性 不准 默认情况下不准,需要人工提供keyshouldComponentUpdate才能准一点
需要前端工程师理解多少API和概念才能正确更新页面 很多 很多 只有@dombind两个概念
总体性能 非常差

这二种体制中,Binding.scala
的确切数据绑定机制概念更少,功效更强,质量更高。我将在下一篇文章中牵线
Binding.scala 怎样在渲染 HTML 时静态检查语法错误和语义错误,从而防止 bug

1 赞 收藏 1
评论

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中的代码就会实施四回。

Binding.scala落成的标签编辑器模板

终极,下文将展现什么用Binding.scala完成标签编辑器。

标签编辑器要比刚刚牵线的HTML模板复杂,因为它不光是静态模板,还含有交互。

JavaScript

@dom def tagPicker(tags: Vars[String]) = { val input: Input =
<input type=”text”/> val addHandler = { event: Event => if
(input.value != “” && !tags.get.contains(input.value)) {
tags.get += input.value input.value = “” } } <section>
<div>{ for (tag <- tags) yield <q> { tag } <button
onclick={ event: Event => tags.get -= tag }>x</button>
</q> }</div> <div>{ input } <button onclick={
addHandler }>Add</button></div> </section> }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@dom def tagPicker(tags: Vars[String]) = {
  val input: Input = &lt;input type="text"/&gt;
  val addHandler = { event: Event =&gt;
    if (input.value != "" &amp;&amp; !tags.get.contains(input.value)) {
      tags.get += input.value
      input.value = ""
    }
  }
  &lt;section&gt;
    &lt;div&gt;{
      for (tag &lt;- tags) yield &lt;q&gt;
        { tag }
        &lt;button onclick={ event: Event =&gt; tags.get -= tag }&gt;x&lt;/button&gt;
      &lt;/q&gt;
    }&lt;/div&gt;
    &lt;div&gt;{ input } &lt;button onclick={ addHandler }&gt;Add&lt;/button&gt;&lt;/div&gt;
  &lt;/section&gt;
}
 

本条标签编辑器的 HTML 模板一共用了 18 行代码就已毕好了。

标签编辑器中必要出示当前所有标签,所以那里用tags: Vars[String]保留所有的竹签数据,再用for/yield循环把tags中的每个标签渲染成UI元素。

Vars
是帮忙数据绑定的列表容器,每当容器中的数据爆发改变,UI就会自行改变。所以,在x按钮中的onclick事件中去除tags中的数据时,页面上的竹签就会自动随之消逝。同样,在Add按钮的onclick中向tags中添加数据时,页面上也会自动发出相应的价签。

Binding.scala不但完结标签编辑器比 ReactJS 简单,而且用起来也比 ReactJS
简单:

JavaScript

@dom def render() = { val tags = Vars(“initial-tag-1”, “initial-tag-2”)
<div> { tagPicker(tags).bind } <h3>全部标签:</h3>
<ol>{ for (tag <- tags) yield <li>{ tag }</li>
}</ol> </div> }

1
2
3
4
5
6
7
8
9
@dom def render() = {
  val tags = Vars("initial-tag-1", "initial-tag-2")
  &lt;div&gt;
    { tagPicker(tags).bind }
    &lt;h3&gt;全部标签:&lt;/h3&gt;
    &lt;ol&gt;{ for (tag &lt;- tags) yield &lt;li&gt;{ tag }&lt;/li&gt; }&lt;/ol&gt;
  &lt;/div&gt;
}
 

假定用 9 行代码另写一个 HTML 模板,在模板中调用刚才达成好的 tagPicker
就行了。

完整的 DEMO 请访问

在 Binding.scala 不需求像 ReactJS 这样编写 changeHandler
之类的回调函数。每当用户在 tagPicker 输入新的价签时,tags
就会改变,网页也就会活动随之变动。

比较 ReactJS 和 Binding.scala 的代码,可以发现以下分别:

  • Binding.scala 的开发者可以用类似 tagPicker 这样的 @dom 方法表示
    HTML 模板,而不要求组件概念。
  • Binding.scala 的开发者能够在措施之间传递 tags 那样的参数,而不必要
    props 概念。
  • Binding.scala 的开发者可以在艺术内定义局地变量表示意况,而不必要
    state 概念。

总的来说 Binding.scala 要比 ReactJS 精简不少。

比方您用过 ASP 、 PHP 、 JSP 之类的服务端网页模板语言, 你会发现和
Binding.scala 的 HTML 模板很像。

运用 Binding.scala 一点也不需求函数式编程知识,只要把设计工具中变化的
HTML 原型复制到代码中,然后把会变的一些用花括号代替、把重复的部分用
for / yield 代替,网页就坚实了。

难点四:ReactJS与服务器通讯时索要复杂的异步编程

ReactJS从服务器加载数据时的架构可以作为MVVM(Model–View–ViewModel)格局。
前端工程师须要编制一个数据库访问层作为Model,把ReactJS的state当做ViewModel,而render作为View。
Model负责访问数据库并把数据设置到state(即View
Model)上,可以用Promise和fetch API实现。
然后,render,即View,负责把View Model渲染到页面上。

在那整个流程中,前端程序员要求编制大批量闭包组成的异步流程,
设置、访问状态的代码五零四散,
一不小心就会bug丛生,即便如履薄冰的拍卖种种异步事件,也会导致程序变得复杂,既难调试,又难保证。

自家将在《More than
React(五)为啥别用异步编程?》中相比较ReactJS和Binding.scala的多少同步模型,介绍Binding.scala怎么样自动同步服务器数据,避免手动异步编程。

有关小编:ThoughtWorks

澳门葡京 3

ThoughtWorks是一家中外IT咨询公司,追求优异软件质量,致力于科学和技术驱动商业变革。擅长创设定制化软件出品,支持客户高效将概念转化为价值。同时为客户提供用户体验设计、技术战略咨询、协会转型等咨询服务。

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

澳门葡京 4

其他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>
}

结论

本文比较了差距技术栈中完结和选择可复用的标签编辑器的难度。

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

Binding.scala
不申明“组件”之类的玩笑,而以更轻巧的“方法”为最小复用单位,让编程体验越来越顺畅,得到了更好的代码复用性。

本连串下一篇作品将相比较 ReactJS 的杜撰 DOM 机制和 Binding.scala
的高精度数据绑定机制,揭开 ReactJS 和 Binding.scala
相似用法背后掩藏的不相同算法。

结论

即使Binding.scala初看上去很像ReactJS,
但隐藏在Binding.scala背后的机制更简约、更通用,与ReactJS和Widok截然差异。

故而,通过简化概念,Binding.scala灵活性更强,能用通用的法门化解ReactJS解决不了的繁杂难题。

比如,除了上述三个方面以外,ReactJS的状态管理也是费力难题,倘若引入Redux或者react-router那样的第三方库来处理状态,会造成架构变复杂,分层变多,代码绕来绕去。而Binding.scala可以用和页面渲染一样的多少绑定机制描述复杂的景色,不必要其他第三方库,就能提供服务器通信、状态管理和网址分发的功效。

以下表格中列出了上述Binding.scala和ReactJS的作用差别:

Binding.scala

ReactJS

复用性

小小的复用单位

方法

组件

复用难度

不管交互内容仍然静态内容都简单复用

不难复用静态内容组件,但麻烦复用交互组件

页面渲染算法

算法

精确的多少绑定

虚拟 DOM

性能

正确性

自行有限支撑科学

需求开发者手动设置 key 属性,不然复杂的页面会混杂。

HTML 模板

语法

Scala XML 字面量

JSX

是不是帮助 HTML 或 XHTML 语法

完全支持 XHTML

残缺协助。正常的 XHTML 不能编译。开发者必须手动把 classfor
属性替换成 classNamehtmlFor,还要把内联的 style 样式从 CSS
语法改成 JSON 语法。

怎么着校验模板语法

自动编译时校验

运行时经过 propTypes 校验但无法检测大约的拼写错误。

服务器通信

机制

自动远程数据绑定

MVVM + 异步编程

贯彻难度

简单

复杂

其他

怎样分摊网址或者锚点链接

帮衬把网址当成普通的绑定变量来用,无需第三方库。

不支持,需求第三方库 react-router

功能完备性

总体的前端开发解决方案

本人只包括视图部分功效。须要极度精通 react-router 、 Redux
等第三方库才能促成完整的前端项目。

上学曲线

API 简单,对没用过 Scala 的人的话也很好懂

上心灵。但意义太弱导致前期学习第三方库时曲线陡峭。

Binding.scala

ReactJS

五个多月前,我在Scala.js的论坛公布Binding.scala时,当时Scala.js社区最风靡的响应式前端编程框架是Widok。提姆Nieradzik是Widok的小编。他在收看我揭橥的框架后,陈赞那个框架是Scala.js社区最有前途的
HTML 5渲染框架。

他是对的,四个月后,现在Binding.scala已经成为Scala.js社区最流行的响应式前端编程框架。

Awesome
Scala网站相对而言了Scala的响应式前端编程框架,Binding.scala的外向程度和流行度都比Udash、Widok等其它框架要高。

澳门葡京 5

自我在近年来的多少个档次中,也逐步废弃JavaScript和ReactJS,改用Scala.js和Binding.scala搭建新时代的前端技术栈。

内嵌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>
}

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

有关链接

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

连带链接

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

强类型的 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()
              ^

至于小编:ThoughtWorks

澳门葡京 6

ThoughtWorks是一家中外IT咨询公司,追求突出软件品质,致力于科学和技术驱动商业变革。擅长营造定制化软件出品,扶助客户快捷将定义转化为价值。同时为客户提供用户体验设计、技术战略咨询、社团转型等咨询服务。

个人主页 ·
我的稿子 ·
84 ·
  

澳门葡京 7

关于作者:ThoughtWorks

澳门葡京 8

ThoughtWorks是一家中外IT咨询公司,追求杰出软件质量,致力于科学技术驱动商业变革。擅长打造定制化软件出品,帮助客户高效将定义转化为价值。同时为客户提供用户体验设计、技术战略咨询、社团转型等咨询服务。

个人主页 · 澳门葡京,
我的篇章 ·
84 ·
  

澳门葡京 9

内联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

澳门葡京 10

ThoughtWorks是一家中外IT咨询公司,追求优异软件质量,致力于科学技术驱动商业变革。擅长营造定制化软件出品,扶助客户高效将概念转化为价值。同时为客户提供用户体验设计、技术战略咨询、协会转型等咨询服务。

个人主页 ·
我的小说 ·
84 ·
  

澳门葡京 11

相关文章

发表评论

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

*
*
Website