Web Components 简述

要说最近几年来,前端开发最火的一个趋势或最火的前端开发框架是什么,第一想到的是,组件及推崇组件化开发的React框架。本文将介绍Web Components规范并就组件的几大特性进行讨论。javascript

前言

在开启本篇的阅读以前,先问一个问题,组件是什么?html

组件,是数据和方法的一个封装,其定义了一个可重用的软件元素的功能,展现和使用,一般表现为一个或一组可重用的元素。前端

组件的特性是什么?一般能够总结为如下几点:java

  1. 可拓展性:既然组件是针对某一特定功能或需求开发的,那它就必须易于开发和拓展;
  2. 封装性:组件做为一个独立总体供使用,应该是对内修改,对外封闭,只供使用,而不对使用环境产生反作用;
  3. 易用性:组件的目的是产生可重用的独立部件,那就必须提供一种简单快捷的方式供使用。

组件化,给前端开发带来了极大的效率提高,是近几年以来web开发发展的趋势,各类组件化的用户界面库,框架也层出不穷,如,React,Vue,Ionic等,这些框架关于组件化都有各自的实现,推崇理念,与编程规范,各大框架的支持者之间的争论也是向来不断,而若想在不一样框架间切换,成本仍是挺高的,由于毕竟谁都但愿本身能占主流,占据绝对优点地位,就像当前IE与网景浏览器之争,延续到如今,各种浏览器标准兼容差别万千,近年来w3c不断在为web标准规范作努力,Web Components就是推出的关于组件化的一个标准,但愿它能将组件化更好的带进web开发,同时尽可能保证标准规范,开发者能够更好的关注于开发,而不是框架选择与争论之上。git

Web components特征

Web Components将一系列特性加入HTML和DOM规范,使得开发者能够自由建立在web应用或文档可重用的元素或部件,其由四部分组成:github

  • 自定义元素(Custom Elements):定义新HTML元素的一系列API;
  • 影子DOM(Shadow DOM):组合对DOM和样式的封装;
  • HTML导入(HTML Imports):定义在文档中导入其余HTML文档的方式;
  • HTML模板(HTML Templates):HTML内的DOM模板,在<template>元素内声明。

自定义元素(Custom Elements)

自定义元素支持开发者定义一类新HTML元素,声明其行为和样式,自定义元素分两类:web

  • 自定义标签元素(Autonomous custom elements):彻底独立于原始HTML元素标签的新标签元素,其全部行为须要开发者定义;
  • 自定义内置元素(Customized built-in):基于HTML原始元素标签的自定义元素,以便于使用原始元素的特性,开发者只须要定义拓展行为;

支持建立自定义元素,Web Components比较好的实现了组件开发的可拓展性。ajax

建立自定义标签元素

为了建立一个自定义标签元素,咱们须要继承HTMLELement类, 如在不少页面咱们常常会有一键回到页面顶部功能,咱们建立一个返回顶部的组件:编程

class GoTop extends HTMLElement {
        constructor() {
            super();
          }
    }
    customElements.define('go-top', GoTop);复制代码

在须要使用该组件的页面只需像使用正常HTML元素同样:浏览器

<go-top>Top</go-top>复制代码

固然,该元素的一切样式,行为,事件监听,默认行为均须要开发者自行定义,没法期待它有像<button>同样的默认行为详细参考建立自定义标签元素

建立自定义内置元素

不少时候咱们并不须要彻底建立一个新元素,而只是须要在某些内置元素基础上进行拓展,建立自定义内置元素,须要继承该类元素类,如HTMLButtonElementHTMLDivElement

class MenuButton extends HTMLButtonElement {
        ...
    }
    customElements.define('menu-button', MenuButton);复制代码

使用也很简单,和内置元素同样的语法;不一样的是,在须要使用自定义内置元素时,为内置元素添加is特性,该特性值对应建立的自定义内置元素名称:

<button is="menu-button">menu</button>复制代码

该元素默认行为继承自<button>元素,可是咱们能够为其设置拓展功能或性质。

对比

经过上面实例可知,自定义标签元素与内置元素主要表如今两点不一样:

  • 标签:自定义标签元素是彻底独立的一个新元素,新标签,而自定义内置标签,使用的依然是已有内置标签;
  • 行为与样式:自定义内置元素,继承内置元素的默认行为,样式及语义,能够进行拓展,而自定义标签元素,彻底须要开发者定义相关声明。

影子DOM(Shadow DOM)

DOM,即文档对象模型,是HTML文档的一个结构表示,以树形结构表示一个文档,文档中元素间关系按照父子,兄弟关系排列;DOM规范提供一系列API支持咱们操做文档节点,即一般所说的DOM API。

前面提到Web Components指封装DOM和样式,以组件的形式在文档中使用,而不一样于JavaScript中函数会造成一个单独做用域,文档DOM树的层次结构中是不存在局部做用域概念的,也就是说文档内全部定义的样式都对整个文档产生影响,文档中的样式也会影响组件内的声明样式,而不限定于元素所处位置,这样显然极大阻碍了组件的独立性和可重用性,是必需要解决的问题,不过不用担忧,这都已经解决了,解决方案就是下文介绍的attachShadow()方法。

影子DOM API提供了attachShadow()方法,建立一个影子DOM,支持将封装的内容或组件做为一个独立DOM子树附加进一个HTML文档,组件内与外部隔离,样式互不影响,这也印证了组件开发的封装性需求。

建立

要建立一个影子DOM,很简单,使用attachShadow()方法便可,而须要注意的是全部影子DOM必须和一个文档中存在的元素(HTML内置元素或自定义元素)绑定,才能使用:

var frag = document.createElement('div');
    var shadowRoot = frag.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = '<p>Shadow DOM Content</p>';复制代码

影子树(shadow tree)与影子主体(shadow host)

上文使用attachShadow()方法建立的元素就是一个影子DOM,而其子内容就构成一棵影子树(shadow tree),而和影子DOM绑定,也就是包含该树的文档内元素一般称为影子主体(shadow host)。

槽位(slot)

如上,当一个元素(即影子主体)内存在影子DOM,浏览器默认只会渲染该影子DOM的影子树,而不渲染影子主体的其余子内容,如,现有某元素<div class="menus">,在文档中使用以下:

<div class="menus">
        <h2>Menus</h2>
    </div>复制代码

给该元素绑定影子DOM:

var menus = document.querySelector('.menus');
    var shadowRoot = menus.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = '<ul>\ <li>Home</li>\ <li>About</li>\ </ul>';复制代码

其影子树内容为:

<ul>
        <li>Home</li>
        <li>About</li>
    </ul>复制代码

最后渲染结果以下:

<div class="menus">
        <ul> <li>Home</li> <li>About</li> </ul>
    </div>复制代码

你好发现影子主体本来的子元素内容没有被渲染,那么是否是没办法了?固然不是,若是要保存子内容,须要使用<slot>槽位元素,至关于作一个占位符,只须要把前文影子主体内容修改成以下:

<div class="menus">
        <h2>Menus</h2>
        <slot></slot>
    </div>复制代码

渲染结果以下, 一切符合需求:

<div class="menus">
        <h2>Menus</h2>
        <ul>
            <li>Home</li>
            <li>About</li>
        </ul>
    </div>复制代码
命名槽(named slots)

上文显示的是只有一个槽位的实例,假如须要有多个分组怎么办呢?Web Components也有解决方案,那就是使用命名槽,即给槽位添加name属性,依然使用如上实例,修改影子主体内容:

<div class="menus">
        <slot></slot>
        <slot name="top"></slot>
        <slot name="right"></slot>
    </div>复制代码

假如影子树内容以下:

<h2>Menus</h2>
    <ul slot="top">
        <li>Home</li>
        <li>About</li>
    </ul>
    <ul slot="right">
        <li>Home</li>
        <li>Top</li>
    </ul>复制代码

渲染结果以下:

<div class="menus">
        <h2>Menus</h2>
        <ul>
            <li>Home</li>
            <li>About</li>
        </ul>
        <ul>
        <li>Home</li>
        <li>Top</li>
    </ul>
    </div>复制代码

如上,能够发现拥有name属性的槽位由对应slot属性值相同的影子子树替换,而剩下的内容默认替换空名槽位,若不存在空名槽位,则剩余内容将被抛弃。

样式

前文已经提到,Web Components定义的组件内的样式与外部环境的样式是互不影响的,那么如何为组件设置样式呢,依然使用<style>标签:

<head>
        <style>
            .top {margin-top: 30px;}
        </style>
    </head>
    ...

    <div class="top">
        ...
    </div>
    ...

    <div class="menus">
        #shadow-root
        <style>
            .top {margin-top: 10px;}
        </style>
        <div class="top">
            ...
        </div>
    </div>复制代码

如上实例,在组件内部top类元素margin-top值为10px,而外部top类元素margin-top值为30px,二者是独立的。

渲染

关于影子DOM树的渲染,其方式与web文档DOM树的渲染方式并没有区别,均由浏览器渲染引擎进行渲染,须要注意的是,影子树的DOM渲染过程和文档DOM树的渲染是独立分别进行的。

HTML引入(HTML Imports)

如何在HTML文档中引入另外一个web文档或web组件呢?像JSP或PHP语言都对HTML语法进行了拓展,咱们可使用诸如<include>标签直接引入另外一个文档,然而在这以前,原生HTML规范并不支持直接引入另外一文档,一般都得经过ajax请求另外一文档内容,而后经过JavaScript使用DOM API将内容插入,对于组件化开发和使用,这样显然不是咱们指望的结果,这与组件的易用性是背离的,因此,HTML imports定义了如何在文档内引入和重用另外一文档。

在文档内直接引入外链资源的文档或web组件,语法以下,使用<link>标签:

<link rel="import" href="components.html">复制代码

假如在components.html中定义了got-top自定义元素,则在本文档内能够直接使用:

<go-top>GoTop</go-top>复制代码

如上,仅仅将<link>标签的rel属性设置成import便可,另外值得注意的是:为了不重复执行引入文档内的脚本,对于已加载文档,import方式将跳过其加载和执行过程。

HTML模板(HTML Templates)

为了更友好的处理组件模板,Web Components规范,支持<template>模板标签,HTML模板定义了使用<template>标签声明能够经过脚本操做插入文档的HTML模板片断:

<template id="menusTemplate">
        <ul> <li>Home</li> <li>About</li> </ul>
    </template>复制代码

使用脚本操做,该元素content属性可访问模板内容:

var menusTemplate = document.querySelector('#menusTemplate');
    var frag = document.importNode(menusTemplate.content, true);
    document.querySelector('.menus').appendChild(frag);复制代码

template标签

<template>标签本质上与其余HTML内置标签同样,可使用DOM API进行操做,可是须要明白,在将模板激活(生成DOM或插入文档)前:

  1. <template>标签内的内容不会被渲染;
  2. 标签内的图片,等媒体资源不会被加载;
  3. 标签不会出如今DOM树,审查元素看不到;

关于兼容性

对于Web Components规范的兼容性,目前仍是须要使用webcomponentsjs polyfills的方式支持开发,总的来讲,目前Safari 10, Google Chrome (53)兼容的更好;虽然兼容性并很差,还在推动过程当中,可是对其进行学习仍是颇有必要的。

类库

推荐几个常见的Web组件类库:

  • Polymer: Google推出的web组件库,支持数据的单向和双向绑定,兼容性较好,跨浏览器性能也较好;
  • X-Tag: 微软推出的开源库,支持Web Components规范,兼容Web ComponentsAPI;
  • Slim.js:轻量级的web组件库,专一于帮助开发者更好的编写原生web组件,而不依赖于其余框架,可是也提供了良好的拓展性,开发者能够自由拓展。

参考

  1. Component
  2. Web Components Specifications
  3. Web Components WiKi
  4. Web Components MDN

欢迎访问个人我的博客 - 熊建刚的博客

相关文章
相关标签/搜索