伤害了复用性,HTML也能够静态编写翻译

伤害了复用性,HTML也能够静态编写翻译。React.Component 损害了复用性?

2016/09/07 · 基础技术 ·
binding.scala,
data-binding,
React,
scala.js

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

本连串的上一篇小说《怎么ReactJS不相符复杂的前端项目》列举了前端开发中的种种痛点。本篇文章大校详细探索在这之中“复用性”痛点。大家将用原生
DHTML API 、 ReactJS 和 Binding.scala
实现同3个内需复用的竹签编辑器,然后相比多个标签编辑器哪个完毕难度更低,哪个更好用。

怎么 ReactJS 不合乎复杂的前端项目?

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

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

《More than
React》种类的稿子会一起分为五篇。本文是首先篇,介绍用ReactJS开发时相遇的各样问题。前边四篇文章的每一篇将会独家详细谈论个中1个难题,以及Binding.scala怎么样化解这些题材。

《More than
React》类别的稿子会一起分成五篇。本文是第二篇,介绍用ReactJS开发时遇上的各种难点。后边四篇小说的每一篇将会分别详细座谈之中叁个难题,以及Binding.scala怎么着缓解那几个题材。

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

标签编辑器的功效供给

在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。

背景介绍

二〇一八年 4 月,我先是次在某些客户的品类中接触到ReactJS 。

我发觉ReactJS要比我原先用过的AngularJS不难很多,它提供了响应式的数量绑定成效,把数据映射到网页上,使作者得以轻松实现互动不难的网站。

唯独,随着小编进一步深远的利用ReactJS,小编发现用ReactJS编写交互复杂的网页很困难。
自己盼望有一种艺术,能够像ReactJS一样简单化解简单难点。除此以外,还要能差不离消除复杂难题。

于是自身把ReactJS用Scala重新写了二个。代码量从近三千0行降到了1000多行。

用那些框架达成的TodoMVC应用,只用了154行代码。而用ReactJS实现均等成效的TodoMVC,需要488行代码。

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

以此框架便是Binding.scala。

其余前端框架的难点

原生 DHTML 版

第3,作者试着不用此外前端框架,直接调用原生的 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 时运营的函数很简单就会争辨,比如1个函数修改了贰个 HTML
成分,日常导致另一处代码受影响而里边景色错乱。

标题一:ReactJS组件难以在复杂交互页面中复用

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

如此的轻量级组件在渲染简单静态页面时很好用,
不过只要页面有互相,就非得在组件间传递回调函数来处管事人件。

自作者将在《More than React(二)组件对复用性有毒?》中用原生DHTML
API、ReactJS和Binding.scala达成同2个急需复用的页面,介绍Binding.scala如何不难完成、简单复用复杂的相互逻辑。

标题一:ReactJS组件难以在千丝万缕交互页面中复用

ReactJS中的最小复用单位是组件。ReactJS的零件比AngularJS的Controller和View
要轻量些。
各种组件只要求前端开发者提供一个 render 函数,把 propsstate
映射成网页成分。

这般的轻量级组件在渲染不难静态页面时很好用,
只是如若页面有相互,就必须在组件间传递回调函数来处总管件。

作者将在《More than React(二)组件对复用性有剧毒?》中用原生DHTML
API、ReactJS和Binding.scala实现同多个急需复用的页面,介绍Binding.scala怎样简单完结、不难复用复杂的交互逻辑。

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

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代码实现了1个标签编辑器组件,即TagPicker。即便代码量比 DHTML
版长了一小点,但复用性大大升级了。

比方你绝不 ECMAScript 二零一五 的话,那么代码还会长一些,而且亟需处理部分
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精确数据绑定机制。

题材二: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精确数据绑定机制。

不协作原生DOM操作

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

Reddit有的是人讨论了那个标题。他们并未主意,只可以弃用jQuery。小编司的某客户在用了ReactJS后也被迫用ReactJS重写了大气jQeury控件。

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就不会报错,往往造成开发者额外耗费大量光阴排查1个很简短的bug。

自小编将在《More than
React(四)HTML也足以编写翻译?》中相比ReactJS和Binding.scala的HTML模板,介绍Binding.scala怎么着在整机支持XHTML语法的同时静态检查语法错误和语义错误。

标题三: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就不会报错,往往导致开发者额外成本大批量时间排查3个很简单的bug。

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

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,与 D三 、jQuery等其它库交互也完全没有毛病。

ReactJS对XHTML语法的欠缺不全。相比较之下,Binding.scala协理完整的XHTML语法,前端工程师能够平昔把设计好的HTML原型复制粘贴到代码中,整个网站就能够运作了。

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 行代码另写3个 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怎么着自动同步服务器数据,幸免手动异步编制程序。

题材四: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如何自动同步服务器数据,制止手动异步编制程序。

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

结论

正文相比较了分歧技术栈中实现和平运动用可复用的价签编辑器的难度。

原生 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那样的第1方库来拍卖意况,会促成架构变复杂,分层变多,代码绕来绕去。而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 + 异步编制程序

贯彻难度

简单

复杂

其他

怎么样分摊网址只怕锚点链接

支撑把网址当成普通的绑定变量来用,无需第3方库。

不帮忙,须要第③方库 react-router

成效完备性

全体的前端开发化解方案

自个儿只包罗视图部分作用。须要格外驾驭 react-router 、 Redux
等第叁方库才能兑现完全的前端项目。

学习曲线

API 容易,对没用过 Scala 的人的话也很好懂

上心灵。但功用太弱导致前期学习第2方库时曲线陡峭。

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等其余框架要高。

金沙澳门官网 3

本人在近年来的多少个档次中,也日益吐弃JavaScript和ReactJS,改用Scala.js和Binding.scala搭建新时期的前端技术栈。

结论

固然Binding.scala初看上去很像ReactJS,
但隐蔽在Binding.scala背后的编制更简短、更通用,与ReactJS和Widok截然区别。

因此,通过简化概念,Binding.scala灵活性更强,能用通用的办法消除ReactJS化解不了的繁杂难点。

例如,除了上述七个地点以外,ReactJS的情状管理也是来之不易难题,如果引入Redux可能react-router那样的第二方库来处理状态,会造成架构变复杂,分层变多,代码绕来绕去。而Binding.scala能够用和页面渲染一样的多少绑定机制描述复杂的景观,不要求其余第一方库,就能提供服务器通讯、状态管理和网址分发的功力。

以下表格中列出了上述Binding.scala和ReactJS的效应差距:

3-sheet.png

多少个多月前,作者在Scala.js的论坛发布Binding.scala时,当时Scala.js社区最流行的响应式前端编制程序框架是Widok。TimNieradzik是Widok的撰稿人。他在察看本身发布的框架后,称扬这么些框架是Scala.js社区最有前景的
HTML 5渲染框架。

她是对的,多少个月后,今后Binding.scala已经变成Scala.js社区最盛行的响应式前端编制程序框架。

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

自小编在近年来的多少个体系中,也稳步吐弃JavaScript和ReactJS,改用Scala.js和Binding.scala搭建新时代的前端技术栈。

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

连锁链接

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

有关链接

  • 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
    聊天室

内嵌Scala代码

而外能够把XHTML内嵌在Scala代码中的 @dom 方法中,Binding.scala 还辅助用
{ ... } 语法把 Scala 代码内嵌到XHTML中。比如:

JavaScript

@dom def randomParagraph = { <p>生成1个随意数: {
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>
}

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

有关作者:ThoughtWorks

金沙澳门官网 4

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

个人主页 ·
作者的稿子 ·
84 ·
  

金沙澳门官网 5

关于笔者:ThoughtWorks

金沙澳门官网 6

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

个人主页 ·
作者的篇章 ·
84 ·
  

金沙澳门官网 7

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

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

金沙澳门官网 8

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

个人主页 ·
作者的小说 ·
84 ·
  

金沙澳门官网 9

发表评论

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

网站地图xml地图