Web|Web Components技术初探

Web|Web Components技术初探
文章图片
image.png 前言 不知不觉,2019年即将接近尾声,现有前端三大框架也各自建立着自己的生态、自己的使用群体。从angular1.0跨时代的开创了前端MVVM模型(在其他平台已经存在的模型,如WPF),到React组件化设计思路的诞生,到Vue借鉴两位前辈的思路,创造属于自己的技术体系。
【Web|Web Components技术初探】随着各大框架版本的更迭,组件化的思路因为大大提高了开发效率依旧一直是各大框架的核心(angular从angular2开始),从未改变。其实,早在react诞生之前,组件化这个概念,已经在2011年前端开发者大会上被提出并完成纳入w3c标准。到现在,基本主流的浏览器都对他进行了兼容。本文便是对这一技术的初探,大家写腻了三大框架,不妨看看原生的组件要怎么玩
Web|Web Components技术初探
文章图片
image.png WebComponent中的三个概念 在WebComponents技术体系中,主要由以下三项技术所组成,通过组合这三项技术,可以创建属于自己功能的组件。

  1. Custom elements(自定义元素) 用于定义自定义标签。
  2. Shadow DOM(影子DOM) 类似于沙盒,将dom结构附加到元素上,保证功能或者样式的私有,而不用担心污染其他功能或者样式。
  3. HTML templates(HTML模板) 可以当做缺少了数据绑定的vue的template标签,主要承担了组件结点渲染,也提供了slot插入内容。
基于以上的内容简介,我们来看看这三项技术具体要怎么使用
Custom elements
Cumtom elements 这个概念对于写惯了三大框架的开发者而言非常的用于理解,自定义标签,我们在其他框架经常通过组件的形式,使用自己定义的标签,就拿vue来举例,我们在vue中会见到下面这样的代码。

在这里,"x-toast"就是一个自定义标签,用于定义自己的功能,对于Web Component,我们可以使用CustomElementRegistry.define方法来自定义元素,该方法接受三个参数
  1. 表示所创建的元素名称的符合DOMString 标准的字符串。注意,custom element 的名称不能是单个单词,且其中必须要有短横线。
  2. 用于定义元素行为的 类
  3. 一个包含 extends属性的配置对象,是可选参数。它指定了所创建的元素继承自哪个内置元素,可以继承任何内置元素。
基于以上的定义,我们可以这样定义一个这样的标签。
CustomElementRegistry.define('todo-list', TodoList);

针对第二个参数TodoList,我们参照参数描述,主要用于定义元素行为的类,他拥有两种类型,通过继承来确定类型方式
  1. **Autonomous custom elements , 独立元素,即html中可以直接使定义的标签,需要继承 ** HTMLElement
class TodoList extend HTMLElement { construct(){ super(); } } CustomElementRegistry.define('todo-list', TodoList);

在html中我们就可以直接这么使用

  1. Customized built-in elements 继承自基本元素,并不像独立元素一样,他依赖于div,p等基本元素标签,通过继承对应的标签,来拓展其功能,具体使用的时候,通过is属性来区分原生标签。
class TodoList extend HTMLParagraphElement { construct(){ super(); } } CustomElementRegistry.define('todo-list', TodoList, {extends: 'p'});

在html中需要配合is属性使用

在这里我们提到了如何定义一个元素(组件),对应vue/react组件,WebComponents也有属于自己的生命周期钩子函数,当我们定义一个元素时,他会在元素的不同阶段触发他们。
  1. connectedCallback:当元素首次被插入文档DOM时,被调用。
  2. disconnectedCallback:当元素从文档DOM中删除时,被调用。
  3. adoptedCallback:当元素被移动到新的文档时,被调用。
  4. attributeChangedCallback: 当元素增加、删除、修改自身属性时,被调用。
在这4个钩子函数中 1、2、4非常好理解,我们都可以从其他框架找到对应,第3可能就比较难于理解,什么叫移动到新的文档时被调用,咱们通过一个例子来说明
function createWindow(srcdoc) { let p = new Promise(resolve => { let f = document.createElement('iframe'); f.srcdoc = srcdoc || ''; f.onload = e => { resolve(f.contentWindow); }; document.body.appendChild(f); }); return p; }// 1. 创建2个Iframe w1,和w2 Promise.all([createWindow(), createWindow()]) .then(([w1, w2]) => { // 2. 在w1这个iframe中创建了一个自定义元素'x-adopt' w1.customElements.define('x-adopt', class extends w1.HTMLElement { adoptedCallback() { console.log('Adopted!'); } }); // 3. 实例化这个自定义元素 let a = w1.document.createElement('x-adopt'); // 4. 将这个自定义元素插入w2这个iframe中 w2.document.body.appendChild(a); });

上面这个例子,便是移动到新的文档中,我在iframe1中创建了一个属于iframe1的新的元素,但是却将他插入iframe2,这样就是将的其插入其他文档,因此会触发adoptedCallback生命周期钩子。
注意在WebComponents的attributeChangedCallback,这个生命周期钩子之中,我们要通过定义observedAttributes这个静态方法,约定你要监听的属性,才会触发attributeChangedCallback回调,如下所示
class CustomInput extends Base{// 定义监听属性 static get observedAttributes() { return ['value']; }// 当自定义元素的一个属性被增加、移除或更改时被调用。: attributeChangedCallback(name, oldValue, newValue) { } }customElements.define('custom-input', CustomInput);

如上面代码所示,当我改变了custom-inputvalue属性时,才会触发attributeChangedCallback,回调,如果你改变了name或者其他非value属性的时候,便不会触发(第一次写Web Components时可能需要注意,第一次本人就怎么也没有办法触发这个回调)
Shadow DOM
Shadow DOM其实并不是一个新的概念,很早之前,Chrome就可以通过控制台Setting显示页面的Shadow DOM

Web|Web Components技术初探
文章图片
image.png
把这一项勾选后,你在通过控制台Elements看看元素,你会发现有一些原生的标签,也有属于自己的Shadow DOM, 如截图中的 input元素。
image.png
#shadow-root称为起始根节点 ,在图中可以看到,他是寄宿在input标签之上,当然这是一个最简单的Shadow DOM,其实 Shadow DOM也和普通元素一样,可以嵌套使用,可以在一个#shadow-root中嵌入别的#shadow-root。如原生标签video就是如此,大家可以自己打开控制台看看。

Web|Web Components技术初探
文章图片
image.png
介绍了这么多Shadow DOM的知识,他的主要功能如上面介绍的,他主要保证功能或者样式的私有,而不用担心污染其他功能或者样式
那么我们来看看他是怎么和Web Components结合使用
对HTML元素而言,他的实例中有一个方法 attachShadow,他会返回shadowRoot并挂载到这个元素实例上,因此我们只需要调用他,便可以生成Shadow DOM;
class TodoList extend HTMLElement { construct(){ super(); const shadowRoot = this.attachShadow({ mode: 'open' }); } } CustomElementRegistry.define('todo-list', TodoList);

我们看见,在调用方法时传入了一个对象,对象中有这样的属性{ mode: 'open' },当mode传入open时,你可以通过元素实体this.shadowRoot获取shadowDOM节点,当传入close时,便没法这样获取,如video标签,你无法通过this.shadowRoot获取到,学过JAVA等面向对象的同学应该会发现,这是不是和将属性定义为private以及public很像呢?
template
template是这三个概念之中最简单的了,即使用