Web Components是不是Web的未来

 

今天 ,Web 组件已经从本质上改变了HTML。初次接触时,它看起来像一个全新的技术。Web组件最初的目的是使开发人员拥有扩展浏览器标签的能力,可以自由的进行定制组件。面对新的技术,你可能会觉得无从下手。那这篇文章将为你揭开Web组件神秘的面纱。如果你已经熟知HTML标签和DOM编程,已经拥有了大量可用的Web组件,那么你已经是Web组件专家了。

Web组件的现状

随着各式各样的用户需求,浏览器的原生组件已经无法满足需求。Web组件也就变得越来越重要。

我们将以自定义一个传统三方插件为例来介绍Web组件。

首先,需要引用插件的CSS和JavaScript资源:

<link rel="stylesheet" type="text/css" href="my-widget.css" />

<script src="my-widget.js"></script>

接下来,我们需要向页面中添加占位符。

<div data-my-widget></div>

最后,我们需要使用脚本来找到并且实例化这个占位符为Web组件。

// 使用 jQuery 初始化组件

$(function() {

$('[data-my-widget]').myWidget();

});

通过以上是三个基本步骤。已经完成了在页面中添加了自定义插件,但是浏览器无法确定自定义组件的生命周期,如果通过以下方式声明则使自定义组件生命周期变得清晰了。

el.innerHTML = '<div data-my-widget></div>';

因为这不是一个内置的组件,我们现在必须手动实例化新组件,

$(el).find('[data-my-widget]').myWidget();

避免这种复杂设置方法的有效方式是完全抽象DOM交互。不过,这个动作也比较复杂,需要创建框架或者库来自定义组件。

面临的问题

组件一旦被声明,占位符已经被替代为原生的HTML标记:

<div data-my-widget>

<div class="my-widget-foobar">

<input type="text" class="my-widget-text" />

<button class="my-widget-button">Go</button>

</div>

</div>

这样做的弊端是,自定义组件的标记和普通HTML组件的标记混杂在一起,没有清晰的分割和封装。这就不可避免的会出现命名及样式等冲突。

Web组件的产生

随着三方Web组件的发展,它已经成为了Web开发不可或缺的部分:

<!—导入: -->

<link rel="import" href="my-widget.html" />

<!—使用:-->

<my-widget />

在这个实例中,我们通过导入HTML来添加组件并且立即使用。

更重要的是,因为<my-widget />是浏览器原生支持的组件,它直接挂在浏览器的生命周期中,允许我们像添加原生组件一样添加三方组件。

el.innerHTML = '<my-widget />';

// 插件当前已经被实例化

当查看这个组件的HTML 源码,你会发现它仅仅是一个单一的标签。如果启用浏览器Shadow DOM 特性,才可以查看标签内的组件,你将会发现一些有趣的事情,

当我们谈论Web组件时,我们不是在谈论一门新技术。Web组件最初的目的是给我们封装能力,它可以通过自定义组件和Shadow DOM 技术来实现。所以,接下来,我们将着重介绍下这两项技术。介绍以上两个技术之前,我们最好先梳理下已知浏览器原生组件。

已知的HTML组件

我们知道组件可以通过HTML标记或JavaScript来实例化:

使用标记实例化:

<input type="text" />
document.createElement('input');
el.innerHTML = '<input type="text" />';

使用JaveScript实例化:

document.createElement('input') 

document.createElement('div')

添加带有属性的HTML标签:

// 创建带有属性的input标签...

el.innerHTML = '<input type="text" value="foobar" />';

//这时value属性已经同步

el.querySelector('input').value;

组件可以响应属性的变化:

// 如果我们更改value 属性值
input.setAttribute('value', 'Foobar'); //属性值会立即更改
input.value === 'Foobar'; // true

组件可以有内部隐藏的DOM结构:

<!—使用一个input实现复杂的日历功能-->
<input type="date" /> // 尽管其内部结构比较复杂,但是已经封装成为一个组件
dateInput.children.length === 0; // true

组件可以使用子组件:

<!—可以给组件提供任意个 'option' 标签-->

<select>

<option>1</option>

<option>2</option>

<option>3</option>

</select>

组件可以为其子组件提供样式:

dialog::backdrop {

background: rgba(0, 0, 0, 0.5);

}

最后,组件可以有内置样式。和自定义插件不同,我们不需要为浏览器的原生控件引用CSS文件。

有了以上的了解,我们已经具备了解Web组件的基础。使用自定义组件和Shadow DOM,我们可以在我们的插件中定义所有这些标准行为。

自定义组件

注册一个新组件也比较简单:

var MyElement = document.register('my-element');

// 'document.register' 返回一个构造函器

你也许注意到上面的自定义组件名称包含一个连接符。这是为了确保自定义组件名称不和浏览器内置组件不冲突。

现在<my-element />这个组件具备了原生组件的特性,

所以,自定义组件也同样可以进行普通的DOM操作:

document.create('my-element');

el.innerHTML = '<my-element />';

document.create('my-element');

构建自定义组件

当前,这个自定义组件仅仅有框架,而没有内容,下面让我们向其中添加一些内容:

//我们将提供'document.register'的第二个参数:

document.register('my-element', {

prototype: Object.create(HTMLElement.prototype, {

createdCallback: {

value: function() {

this.innerHTML = '<h1>ELEMENT CREATED!</h1>';

}

}

})

});

在这个例子中,我们设置自定义组件的prototype,使用Object.create 方法创建一个继承于HTMLElement的对象。在这个方法中修改该组件的属性 innerHTML。

我们定义了createdCallback方法,在每次声明实例时调用。你同样可以有选择性的定义attributeChangedCallback、 enteredViewCallback 和leftViewCallback等方法。

目前为止我们实现了动态修改自定义组件内容的功能,我们仍然需要提供自定义组件的封装方法,用于隐藏其内部组件。

使用Shadow DOM实现封装

我们需要完善下createdCallback方法。本次,除了修改innerHTML之外,我们添加一些额外的操作:

createdCallback: {

value: function() {

var shadow = this.createShadowRoot();

shadow.innerHTML = '<h1>SHADOW DOM!</h1>';

}

}

在这个例子中, 你会注意到‘SHADOW DOM!’,但是查看源码时你会发现只有空白的<my-element /> 标签而已。这里使用创建Shadow Root 方法替代了直接修改页面。

Shadow Root中的任何组件,是肉眼可见的,但是和当前页面的样式和DOM API相隔离。这样就实现了自定义组件是一个独立组件的假象。

添加“轻量级DOM”

目前为止,我们的自定义组件是空标签,但是如果向其中添加内部组件会出现什么现象呢?

我们假设自定义组件包含的节点如下,

<my-element>

这是一个轻量级 DOM。

<i>hello</i>

<i>world</i>

</my-element>

一旦针对于这个组件的 Shadow Root 被创建,它的子节点不再存在。我们这些隐藏的子节点封装为轻量级DOM节点。

如果禁用了 Shadow DOM,上面这个例子仅仅会显示为:这是一个轻量级 DOM‘hello world’。

当我们在createdCallback方法中设置 Shadow DOM后,我们可以使用新增内容分配轻量级DOM组件到Shadow DOM 中。

createdCallback: {

value: function() {

var shadow = this.createShadowRoot();

// 子组件'i' 标签现在已经消失了

shadow.innerHTML =

‘轻量级 DOM 中的 "i" 标签为: ' +

'<content select="i" />';

//现在,在 Shadow DOM 中只有 'i' 标签是可以见的。

}

}

封装样式

Shadow DOM 最重要的作用是创建了和当前页面隔离的Web组件,使Web组件不受当前页面样式和JaveScript脚本的影响。

createdCallback: {

value: function() {

var shadow = this.createShadowRoot();

shadow.innerHTML =

"<style>span { color: green }</style>" +

"<span>I'm green</span>";

}

}

反之,在 Shadow DOM 中定义的样式也不会影响之外的标签样式。

<my-element />

<span>I'm not green</span>

揭露钩子的秘密

当隐藏自定义组件内部标记,有时也需要在当前页面对组件中的内部特定组件进行样式设置。

例如,如果我们自定义一个日历插件,在不允许用户控制整个插件的情况下,允许最终用户去定义按钮的样式。

这是其中的部分特性和伪组件:

createdCallback: {

value: function() {

var shadow = this.createShadowRoot();

shadow.innerHTML = 'Hello <em part="world">World</em>';

}

}

这是在当前页面设置自定义组件内部组件样式的方法:

my-element::part(world) {

color: green;

}

这部分内容介绍了封装web组件的基本方式。Shadow DOM 是我们可以任意修改Web组件中的标签。在例子中,我们设置了“World”的样式,但是使用者却无法判断它是<em>标签。

在你尝试自定义Web组件之前,需要确保浏览器的相关特性已经打开。如果使用 Chrome,在 Chrome 中打开chrome://flags ,并且开启“experimental Web Platform features”。

这仅仅是个开始

所有本文中介绍的内容,都是模拟一些简单的浏览器标准行为。我们已经习惯于和原生的浏览器组件进行交互,因此自定义组件的步骤并不是想象中的那个难。Web组件最终提供我们一种实现简单、一致、可复用、封装和组合部件的方法,这是一个有意义的开始。

Knockout与Require框架同时使用时的visible绑定的问题,造成的影响,以及解决的方法。

Knockout 可以将 visible 绑定到DOM 元素上,使得该元素的hidden 或visible 状态取决于绑定的值。

查看以下knockout的描述,http://knockoutjs.com/documentation/visible-binding.html

When the parameter resolves to a false-like value (e.g., the boolean value false, or the numeric value 0, or null, or undefined),  <br>the binding sets yourElement.style.display to none, causing it to be hidden. This takes priority over any display style you’ve defined using CSS。
当这个参数是一个假值时(举例来说,布尔值的false , 数值0,或者null,或者undefined),绑定时候设置你的元素的style.display是none,从而使之隐藏起来。这个优先级要高于CSS中定义的.
 
When the parameter resolves to a true-like value (e.g., the boolean value true, or a non-null object or array),  the binding removes the yourElement.style.display value, <br>causing it to become visible。
当这个参数是一个真值时(举例来说,布尔值是true,或者非空对象与数组),绑定时候移除你display的值,从来显示出来

当单独使用knockout框架时,这个visible绑定运行起来相当好,但是,当Knockout和Require两个框架同时使用时,就出问题了。

先看一个例子,这个例子里有有两个div,第一次加载页面时显示div1,隐藏div2,当按下一步按钮时,隐藏div1,显示div2,最后点返回按钮时,显示div1,隐藏div2。

ko_visible.htm代码,

<html>
<head>
   <script src="../lib/require/require.js" data-main="ko_visible"></script>
</head>
<body  >
  <div id="div1" data-bind="visible: showVisibleDiv1">
  <p>First name: <span ></span></p>
  <p>Last name: <span ></span></p>
  <p>Full name: <span ></span></p>
  <input type="text" id="inputAddress"  /> *
  <input type="text" id="inputMoney"  /> *
  <input type="button" id="btnSave" value="下一步" data-bind="click: SaveClick" />
  </div>
 
  <div id="div2" data-bind="visible: showVisibleDiv2">
      <table>
          <tr>
              <td>标题</td><td>内容</td>
          </tr>
          <tr>
              <td></td><td></td>
          </tr>
      </table>
      <input type="button" id="btnNext" value="返回" data-bind="click: NextClick" />
  </div>
 
</body>
</html>

  

ko_visible.js

require.config({
    paths: {
        "knockout": "../lib/knockout/knockout-2.3.0",
        "jquery": "../lib/jquery/jquery-1.9.1.min"
    }
 
});
 
require(['jquery', 'knockout'], function ($, ko) {
 
    //数据绑定
    $(document).ready(function () {
        var viewModel = {
            showVisibleDiv1: ko.observable(true),
            showVisibleDiv2: ko.observable(false),
            SaveClick: function () {
                viewModel.showVisibleDiv1(false);
                viewModel.showVisibleDiv2(true);
            },
            NextClick: function () {
              viewModel.showVisibleDiv1(true);
              viewModel.showVisibleDiv2(false);               
            }
        };
        ko.applyBindings(viewModel); 
    });
});

当运行此页面时,效果如下图,div1,div2同时显示,然后, div2因为visible=false的缘故,又迅速消失。

当页面中存在多个步骤的div,想一步步执行并控制某些div显示时, 这个效果是让人不能接受的.

当页面初始加载时,因为Require的延迟加载特性(也就是不先加载js,而是先加载元素,异步加载js),当knockout代码还没有执行时,元素是没有被隐藏的,这一点是致命的。

所以,修改的方法有两个:

1.不使用Require框架。

例子如下: 

ko_visible1.htm

<html>
<head>
  <script type="text/javascript" src="../lib/knockout/knockout-2.3.0.js"></script>
  <script type="text/javascript" src="../lib/jquery/jquery-1.9.1.min.js"></script>
  <script type="text/javascript" src="ko_visible1.js"></script>
</head>
<body  >
  <div id="div1" data-bind="visible: showVisibleDiv1">
  <p>First name: <span ></span></p>
  <p>Last name: <span ></span></p>
  <p>Full name: <span ></span></p>
  <input type="text" id="inputAddress"  /> *
  <input type="text" id="inputMoney"  /> *
  <input type="button" id="btnSave" value="下一步" data-bind="click: SaveClick" />
  </div>
 
  <div id="div2" data-bind="visible: showVisibleDiv2">
      <table>
          <tr>
              <td>标题</td><td>内容</td>
          </tr>
          <tr>
              <td></td><td></td>
          </tr>
      </table>
      <input type="button" id="btnNext" value="返回" data-bind="click: NextClick" />
  </div>
 
</body>
</html>

ko_visible1.js

//数据绑定
$(document).ready(function () {
 
    var viewModel = {
        showVisibleDiv1: ko.observable(true),
        showVisibleDiv2: ko.observable(false),
        SaveClick: function () {
            viewModel.showVisibleDiv1(false);
            viewModel.showVisibleDiv2(true);
        },
        NextClick: function () {
          viewModel.showVisibleDiv1(true);
          viewModel.showVisibleDiv2(false);               
        }
    };
    ko.applyBindings(viewModel); 
});

  

2.如果必须使用Require框架,那在这种场合,建议不要使用visible,还是使用css的样式控制。

例子如下:

ko_display.htm

<html>
<head>
   <script src="../lib/require/require.js" data-main="ko_display"></script>
</head>
<body  >
  <div id="div1" data-bind="visible: showVisibleDiv1">
  <p>First name: <span ></span></p>
  <p>Last name: <span ></span></p>
  <p>Full name: <span ></span></p>
  <input type="text" id="inputAddress"  /> *
  <input type="text" id="inputMoney"  /> *
  <input type="button" id="btnSave" value="下一步" data-bind="click: SaveClick" />
  </div>
 
  <div id="div2" style="display:none">
      <table>
          <tr>
              <td>标题</td><td>内容</td>
          </tr>
          <tr>
              <td></td><td></td>
          </tr>
      </table>
      <input type="button" id="btnNext" value="返回" data-bind="click: NextClick" />
  </div>
 
</body>
</html>

ko_display.js

require.config({
    paths: {
        "knockout": "../lib/knockout/knockout-2.3.0",
        "jquery": "../lib/jquery/jquery-1.9.1.min"
    }
 
});
 
require(['jquery', 'knockout'], function ($, ko) {
 
    //数据绑定
    $(document).ready(function () {
 
        var viewModel = {
            showVisibleDiv1: ko.observable(true),
 
            SaveClick: function () {
                viewModel.showVisibleDiv1(false);
                $("#div2").show();
            },
            NextClick: function () {
                viewModel.showVisibleDiv1(true);
                $("#div2").hide();                          
            }
        };
        ko.applyBindings(viewModel); 
    });
});

以上是本人在项目中遇到的问题总结,如有谬误之处,还请大家指正!

 
 
 
分类: Web技术

Web Components的更多相关文章

  1. 【shadow dom入UI】web components思想如何应用于实际项目

    回顾 经过昨天的优化处理([前端优化之拆分CSS]前端三剑客的分分合合),我们在UI一块做了几个关键动作: ① CSS入UI ② CSS作为组件的一个节点而存在,并且会被“格式化”,即选择器带id前缀 ...

  2. Web Components初探

    本文来自 mweb.baidu.com 做最好的无线WEB研发团队 是随着 Web 应用不断丰富,过度分离的设计也会带来可重用性上的问题.于是各家显神通,各种 UI 组件工具库层出不穷,煞有八仙过海之 ...

  3. Web Components之Custom Elements

    什么是Web Component? Web Components 包含了多种不同的技术.你可以把Web Components当做是用一系列的Web技术创建的.可重用的用户界面组件的统称.Web Com ...

  4. 【转】Facebook React 和 Web Components(Polymer)对比优势和劣势

    原文转自:http://segmentfault.com/blog/nightire/1190000000753400 译者前言 这是一篇来自 StackOverflow 的问答,提问的人认为 Rea ...

  5. 可选的Web Components类库

    首先需要说明的是这不是一篇 Web Components 的科普文章,如果对此了解不多推荐先读<A Guide to Web Components>. 有句古话-“授人以鱼,不如授人以渔” ...

  6. Polymer——Web Components的未来

    什么是polymer? polymer由谷歌的Palm webOS团队打造,并在2013 Google I/O大会上推出,旨在实现Web Components,用最少的代码,解除框架间的限制的UI 框 ...

  7. The state of Web Components

    Web Components have been on developers’ radars for quite some time now. They were first introduced b ...

  8. 前端应该知道的Web Components

    前端组件化的痛点 在前端组件化横行的今天,确实极大的提升了开发效率.不过有一个问题不得不被重视,拟引入的这些html.css.js代码有可能对你的其他代码造成影响. 虽然我们可以通过命名空间.闭包等一 ...

  9. 一个使用 Web Components 的音乐播放器: MelodyPlayer

    先上效果预览: Web Components 首先,什么是 Web Components ? MDN 给出的定义是: Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的 ...

随机推荐

  1. Android经常使用的布局类整理(一)

    Android经常使用的布局类整理 近期又回头做了一下android的项目,发觉越来越不从心,非常多东西都忘了,简单的页面布局也非常多写不出来,首先还是先整理一下一些会混淆的概念先 layout_wi ...

  2. tortoise svn无法识别subversion check向下代码来解决

    使用eclipse小工具subversion check代码后,tortoise svnclient(版本号1.8.8)无法识别,不显示svn图标. 根据每个试验后的线上不实际的解决方案.试过的方法: ...

  3. crawler_分布式网络爬虫的设计与实现_设计图

    一.集中调度式 二.p2p 三.混合调度式 四.大型集群

  4. mysql_【MySQL】常见的mysql 进程state

    Analyzing 线程是对MyISAM 表的统计信息做分析(例如, ANALYZE TABLE ). checking permissions 线程是检查服务器是否具有所需的权限来执行该语句. Ch ...

  5. CSharp设计模式读书笔记(19):备忘录模式(学习难度:★★☆☆☆,使用频率:★★☆☆☆)

    备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.它是一种对象行为型模式,其别名为Tok ...

  6. [CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性

    原文:[CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性 1.6 Framework类库 1. .NET Framework中包含了Framework类库(Frame ...

  7. UIAppDelegate介绍

    #import "GLAppDelegate.h" @implementation GLAppDelegate // 当应用程序启动完毕的时候就会调用(系统自动调用) - (BOO ...

  8. Unofficial Microsoft SQL Server Driver for PHP (sqlsrv)非官方的PHP SQL Server 驱动

    原文 Unofficial Microsoft SQL Server Driver for PHP (sqlsrv) Here are unofficial modified builds of Mi ...

  9. 探秘IntelliJ IDEA v13的应用服务器

    原文:探秘IntelliJ IDEA v13的应用服务器 IntelliJ IDEA v13应用out-of-the-box支持众多企业级和开源的服务器,包括:GlassFish.WebLogic. ...

  10. DDD分层架构之聚合

    DDD分层架构之聚合 前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可 ...