几行代码就能完成 Web 组件的数据绑定

更新日期: 2019-08-05阅读: 2k标签: 数据

这不是什么难事,一般来说没必要动用虚拟 DOM。今年早些时候我写了一篇文章,声称 Web 组件最终将取代前端框架 。

这篇文章引起了很多争议,这大大出乎我的意料,但也让我收获良多。有很多人同意我的观点,也有很多人持否定态度,甚至有人觉得我根本就是脑子进水,应该永远禁止我再写代码了。总的来说,争论的双方都提出了很不错的观点。批评声音主要指出现有的框架提供了一种通过数据绑定编写视图的声明式途径,这是原生 Web 组件天生不具备的能力。这一观点本身没错,但其实 Web 组件是很容易实现数据绑定的,我将在本文中演示具体做法。


声明式数据绑定的情况

数据绑定最早是被 Angular、Backbone 和 Ember 等框架推广而流行开来的,现在则在某种程度上是编写视图的标准途径。它能让“视图作为数据的函数”,意味着每当某些数据发生变化时,相关视图将“自动”更新。

不需要冗长的 DOM 操作来保持数据和视图同步,只需更新数据,视图就会随之变化。这是一项杀手级功能,如今但凡理智的开发人员就会用它。所以很容易理解为什么开发人员会使用提供了数据绑定功能的框架,就算框架对于应用来说太大材小用也无所谓:既然框架打理好了一切,何必要费心费力处理那些麻烦的 DOM 操作呢?

但数据绑定并不是什么魔法,你用不着为了用它而动用整个框架。在 Web 组件里只需要几行代码就能轻松搞定数据绑定了,没什么特殊的。


如何实现

就像我上面说的那样,数据绑定并没有那么神奇。当基础数据发生变化时,你的视图不是凭空“神奇”地更新的。在框架深处,不为人知的某个角落里的设置代码负责在数据更改时调用并更新视图。

AngularJS 使用了所谓的“摘要循环”:这是一种粗暴的检查机制,不断检查哪些数据已更改,以便随时更新对应的视图。

React 面世时提供了另一种据称性能更好的解决方案,称为虚拟 DOM:它是一种 DOM 的 JavaScript 表示,只更新已更改的 DOM 部分。这对列表来说很合适——列表的少数项目发生变化时无需重新渲染整个列表,只需更新已更改的项目。


哪种工具最合适?

这对于具有复杂 UI 的较大应用程序来说非常有用,但对于大多数应用程序来说实在有些杀鸡用牛刀了。编写一些监视数据的代码并在数据发生变化时更新相关视图并不是什么难事。问题是这些数据通常需要传递给也需要数据绑定的子组件,所以最后往往会有很多 DOM 操作。

你需要的是一种在数据被推送到子组件时触发子组件中相同的数据绑定操作的方法。只要父组件的数据发生更改并且某些数据绑定到子组件的视图,该子组件的视图也需要更新。

一种方法是利用所有组件的基类,因为 Web 组件是使用 JavaScript 类创建的,所以这是一个很好的选择。默认情况下 Web 组件扩展 HTMLElement,但我们也可以创建自己的基类来扩展 HTMLElement,如下所示:

exportclassCustomElementextendsHTMLElement

然后我们创建的每个 Web 组件都扩展了这个 CustomElement 基类:

exportclassMyComponentextendsCustomElement

如果你想直接查看代码,可以访问 Github 链接 。

我们将 CustomElement 的内部 state 属性绑定到视图来实现数据绑定:

classCustomElementextendsHTMLElement{
constructor() {
super();
this.state = {};
}
}

我的第一个想法是将 this.state 实现为 Proxy,这样 state 对象的任何突变都将被自动拦截;但由于 Proxy 可能会影响性能,因此我决定实现一个 setter,它还能同时设置多个属性:

classCustomElementextendsHTMLElement{
...
setState(newState) {
Object.entries(newState)
.forEach(([key, value]) => {
this.state[key] =this.isObject(this.state[key]) &&
this.isObject(value) ? {...this.state[key], ...value} : value;
});
}
}

setState 方法遍历 newState 对象的所有条目,并将所有值设置为 this.state 上的对应属性,随后我们就应该使用这些值来更新视图。

通过标准 data 属性将值绑定到视图上,在本例中为 data-bind:

<pdata-bind="title"></p>

这里的 textContent 绑定到负责管理视图的组件内的 this.state.title 的值上:

classDemoElementextendsCustomElement{
constructor() {
super();
const shadowRoot =this.attachShadow({mode:'open'});
shadowRoot.innerHTML = `
<p data-bind="title"></p>
`;
}
}
const element = document.querySelector('demo-element');
element.setState({title:'HelloWorld'});
// the paragraph will now contain the text "Hello World"

这种绑定可以达到任意深度,所以下面这种情况也能做到:

<pdata-bind="user.address.city"></p>
element.setState({
user: {
address: {
city: 'Amsterdam'
}
}
});
---><p>Amsterdam</p>

还可以将数据绑定到 Web 组件的特定属性。在此示例中,数据绑定到 的 title 属性上:

<parent-element>
<demo-elementdata-bind="title:name"></demo-element>
</parent-element>
parentElement.setState({name: 'foo'}); //demoElement.title === 'foo'

视图的更新实际是在 CustomElement 中的 updateBindings 方法中实现的。通过 setState 方法更新 state 时,它会解析更新的属性以查找绑定到这些属性的 HTML 元素。

例如下面这样:

element.setState({
user: {
address: {
city:'Amsterdam'
}
}
});

更新 Web 组件内的 this.state.user.address.city,并将数据对象中的键转换为 user.address.city,然后使用它来查找这个数据绑定的元素:

constelements =this.shadowRoot.querySelectorAll('[data-bind$="user.address.city"]');

这将查找所有的 data-bind 属性以 user.address.city 结尾的元素(注意 data-bind 

 ),因此它将找到 data-bind=“user.address.city”,但也可以找到 data-bind=“ name

:user.address.city“,其中数据专门绑定到 name 属性。

每当数据绑定到元素的特定属性(如 data-bind=“name:user.address.city”)时,组件将检查该元素是否也是扩展 CustomElement 的 Web 组件;如果是,则通过它的 setState 方法更新该属性。这样,数据绑定就能一直传播到所有子组件上。

如果绑定数据的元素是常规 HTML 元素,那么将简单地更新其 textContent。在这两种情况下,只需几行代码即可有效实现 DOM 的更新。


列表该怎么处理?

像虚拟 DOM 这样的解决方案真正的用武之地是渲染列表。例如只更改列表的一部分时,虚拟 DOM 将仅更新已更改的部分,而不是重新渲染整个列表。

其机制是在第一次渲染列表时创建 DOM 节点,然后在列表更改时只更新这些现有节点(textContent、属性等)。复用已经创建的节点比重新渲染整个列表重建所有节点的开销小得多,因此对于非常大的列表来说这种方法效率更高。

但如果你的列表平均只有 25 个项目时,你可能会想知道重新渲染整个列表能比虚拟 DOM 的方法慢多少。当你渲染 250 个项目时可能会变得很慢,但理智的开发人员在这种情况下就应该分页了。

我不是说大家就应该抛弃虚拟 DOM,因为它的确是很棒的技术。如果遇到真正需要虚拟 DOM 的情况,自然一定要使用它。我只是希望大家遇到比较轻量的问题时先思考一下能不能找到一个比较轻量的解决方案,而不是上来就动用重量级的杀手锏。

customElement 演示的 Github 仓库 包含一个 Web 组件,它在 li 标签内渲染任何设置为其 items 属性的字符串数组。只要将 items 设置为新数组它就会重新渲染整个列表,但是复用现有的 li 标记也很简单。


结论

答案很清楚了,只需几行代码即可对 Web 组件进行声明式数据绑定。我觉得自己已经清楚地证明了数据绑定很容易实现,并且你不需要动用整个框架也能使用它。

上面提到的 Github 仓库中的代码不是 React 或 Vue.js 等框架的替代品,本来也没这个意思。框架提供的并不只有数据绑定,本文和涉及的代码是为了证明你不一定需要一个框架来实现声明式数据绑定。

除了数据绑定之外,customElement 还提供了一些方便的方法来选择元素以及显示和隐藏元素。

我请大家好好看看我的代码,仔细研究一下,能给我反馈的话我会感激不尽!

英文原文: https://medium.com/swlh/https-medium-com-drmoerkerke-data-binding-for-web-components-in-just-a-few-lines-of-code-33f0a46943b3


链接: https://www.fly63.com/article/detial/5160

双向数据绑定与单向数据绑定的各自优势和关系

在react中是单向数据绑定,而在vue和augular中的特色是双向数据绑定。为什么会选择两种不同的机制呢?我猜测是两种不同的机制有不同的适应场景,查了一些资料后,总结一下。

原生JS数据绑定的实现

双向数据绑定是非常重要的特性 —— 将JS模型与HTML视图对应,能减少模板编译时间同时提高用户体验。我们将学习在不使用框架的情况下,使用原生JS实现双向绑定 —— 一种为Object.observe

JavaScript判断数据类型的多种方法【 js判断一个变量的类型】

js判断数据类型的多种方法,主要包括:typeof、instanceof、 constructor、 prototype.toString.call()等,下面就逐一介绍它们的异同。

javascript中的typeof返回的数据类型_以及强制/隐式类型转换

由于js为弱类型语言拥有动态类型,这意味着相同的变量可用作不同的类型。 typeof 运算符返回一个用来表示表达式的数据类型的字符串,目前typeof返回的字符串有以下这些: undefined、boolean、string、number、object、function、“symbol

使用typeof obj===‘object’潜在的问题,并不能确定obj是否是一个对象?

在js中我们直接这样写typeof obj===‘object’有什么问题呢?发现Array, Object,null都被认为是一个对象了。如何解决这种情况,能保证判断obj是否为一个对象

js进制数之间以及和字符之间的转换

js要处理十六进制,十进制,字符之间的转换,发现有很多差不多且书写不正确的方法.一个一个实践才真正清楚如何转换,现在来记录一下它们之间转换的方法。

js判断数字是奇数还是偶数的2种方法实现

奇数和偶数的判断是数学运算中经常碰到的问题,这篇文章主要讲解通过JavaScript来实现奇偶数的判断。2种判断方法:求余% 、&1

js算法_判断数字是否为素数/质数

质数又称素数。指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。比如100以内共25个,js实现代码如下。

Js数据类型转换_JavaScript 那些不经意间发生的数据类型自动转换

JavaScript自动类型转换真的非常常见,常用的一些便捷的转类型的方式,都是依靠自动转换产生的。比如 转数字 : + x 、 x - 0 , 转字符串 : \\\"\\\" + x 等等。现在总算知道为什么可以这样便捷转换。

Js中实现XML和String相互转化

XML是标准通用标记语言 (SGML) 的子集,非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。 这篇文章主要介绍Js中实现XML和String相互转化

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!