控件设计的方案权衡

2018-05-17 admin

1. 双向绑定

看到这个词,相信在不少人的脑子里都会冒出某种现代前端框架吧?但是扯到任何一种现代前端框架恐怕都会引战,所以我还是喜欢从原生控件说起。

原生控件就是以双向绑定的风格来设计的,比如最典型的 input[type=text]。当用户通过 UI 操作来修改其值的时候,它的 value 属性会同时发生变化;当程序修改 value 属性的时候,UI 也会发生相应变化。所以整体看起来,value 属性和 UI 的变化就是双向绑定在一起的。

我猜肯定会有人发出这样的疑问:

双向绑定的概念应该是 view 和 model 之间的绑定才对吧?

model 层应该是一个相对的概念,也许是因为写多了业务代码的缘故,在很多人心中 model 的概念都是业务上的。但作为公共控件的设计者,控件本身就是全部,所以可以认为 value 属性就是这个控件的 model 层。

接下来可能会遇到疑问二连击:

如果这么解释,那所有控件都是双向绑定了?

这个问题同样和看 model 层的角度有关。比如上面例子中的 input[type=text],如果你只关心用户输入的内容、只关心 value 属性,那确实是双向绑定的。但如果你还关心光标的位置呢?光标位置变化同样属于 UI 变化,早期的 IE 上并没有 selectionStartselectionEnd 这样的属性,所以可以说当时并不是双向绑定的。甚至早期 select 控件的 value 都不是双向绑定的。

现在大部分控件确实都是双向绑定了,因为这种设计是最便于理解和使用的。

2. 如何实现?

那么如何实现双向绑定的控件呢?

有两种方案(方向),我们通过一个例子来分析一下吧。

假如要实现一个计数按钮控件(纯粹举例子,没啥应用场景),就是一个带数字的按钮,每次点击后上面的数字自增,并且把这个数字绑定到 value 属性上。

方案 1:依赖后代的数据绑定

<script>
class Counter {
  // 从后代获取值
  get value() {
    return +this.button.textContent;
  }
​
  // 将值设置到后代上
  set value(value) {
    this.button.textContent = +value;
  }
​
  constructor() {
    let button = this.button = document.createElement('button');
    button.type = 'button';
    this.value = 0;
​
    // 用户操作直接更新到后代的 UI 上
    button.addEventListener('click', () => {
      this.button.textContent = +this.button.textContent + 1;
    });
  }
​
  renderTo(parent) {
    parent.appendChild(this.button);
  }
}
​
addEventListener('load', () => {
  let counter = new Counter();
  counter.renderTo(document.body);
});
</script>

这个方案的问题在于对 UI 的写操作不止一处,当触发 UI 变化的事件太多或 UI 变化前需要额外计算时,整个逻辑就会非常混乱。想象一下 input[type=number] 这种控件,点击和输入都可以影响 UI,而且输入的数据不仅要考虑类型,还要考虑 maxmin

方案 2:单向数据流

<script>
class Counter {
  get value() {
    return this.$value;
  } 

  // 所有影响 UI 的操作都从 set value 开始
  set value(value) {
    this.$value = +value;
    this.button.textContent = this.$value;
  }

  constructor() {
    let button = this.button = document.createElement('button');
    button.type = 'button';
    this.value = 0;
    // 用户操作不直接影响 UI,而是反馈到 set value 上
    button.addEventListener('click', () => {
      this.value++;
    });
  }

  renderTo(parent) {
    parent.appendChild(this.button);
  }
}
​
addEventListener('load', () => {
  let counter = new Counter();
  counter.renderTo(document.body);
});
</script>

这个方案将所有 UI 操作都放在了 set value 中,并且 get value 直接将值返回,什么也不用做,逻辑变得简单很多。

但是这个方案也是有坑点的。因为任何一个局部的 UI 变化都要考虑全局的 UI 更新。如果全局的 UI 更新是一个高成本的操作(比如超大的树形控件),或者局部 UI 高频变化(比如大量 input 事件)。那么这个方案就会导致性能非常差。

3. 总结

曾经有一段时间我很迷信单向数据流,总觉得这样可以简化逻辑,降低维护成本。直到强行用单向数据流去设计复杂控件(当时是要设计一个节点支持文本输入的树)遇到无法满足的场景之后才开始反思这两种控件设计方案的适用场景。

这篇文章是想告诉大家,**控件设计的这两个方案其实是两个方向的极端,在这两个极端的中间存在着无数种方案。**必要时可以考虑同时使用两种方案结合。比如在全局使用单向数据流,局部依赖后代的数据绑定加以补偿。

原文链接:https://zhuanlan.zhihu.com/p/36513856

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-32381.html

文章标题:控件设计的方案权衡

相关文章
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
ajax为什么令人惊异?ajax的优缺点
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 Ajax不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。就像DHT...
2015-11-12
HTML5的5个不错的开发工具推荐
HTML5规范终于在今年正式定稿,对于从事多年HTML5开发的人员来说绝对是一个重大新闻。数字天堂董事长,DCloud CEO王安也发表了文章,从开发者和用户两个角度分析了HTML对两个人群的优势。其实,关于HTML5的开发工具,我们以往的...
2015-11-12
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
AJAX的浏览器支持
AJAX 的要点是 XMLHttpRequest 对象。 不同的浏览器创建 XMLHttpRequest 对象的方法是有差异的。 IE 浏览器使用 ActiveXObject,而其他的浏览器使用名为 XMLHttpRequest 的 Jav...
2015-11-12
Riot.js:不足1KB的MVP客户端框架
Riot.js是一款MVP(模型-视图-呈现)开源客户端框架,其最大的特点就是体积非常小,不足1KB,虽然体积小,但它可以帮助用户构建大规模的Web应用程序。 Riot.js是由Moot公司开发,目前最新版本为v0.9.2,遵循MIT开源许...
2016-03-11
JavaScript的组成
一个完整的JavaScript由3个部分组成:核心(ECMAScript) 文档对象模型(DOM) 浏览器对象模型(BOM) ECMAScript 描述了该语言的语法和基本对象 ; DOM 描述了处理网页内容的方法和接口 ; BOM 描...
2015-11-12
typeof、instanceof和contructor的区别
typeof:以字符串的形式返回变量的原始类型,typeof在两种情况下会返回&quot;undefined&quot;:一个变量没有被声明的时候,和一个变量的值是undefined的时候,注意,typeof null也会返回object,...
2015-11-12
必须记住的 30 类 CSS 选择器
开篇 有 30 个 CSS 选择器你必须烂熟于心,它们适应于当今各大主流浏览器。 1.* * { margin: 0; padding: 0; } *选择器选择的是每一个单一元素。很多程序员用上面的 CSS 将所有元素的 ma...
2015-11-16
JavaScript变量的声明
声明变量 变量在脚本中的首次亮相是在其声明中。 在变量首次出现时将会在内存中设置它,因此您稍后可在脚本中引用它。 应在使用变量之前先声明变量。 可以使用 var 关键字实现此目的。 &lt;span id=“mt9” class=“sent...
2015-11-12
回到顶部