前言

 最近加入到新项目组负责前端技术预研和选型,一直偏向于以Polymer为代表的WebComponent技术线,于是查阅各类资料想说服老大向这方面靠,最后得到的结果是:"资料99%是英语无所谓,最重要是UI/UX上符合要求,技术的事你说了算。",于是我只好乖乖地去学UI/UX设计的事,木有设计师撑腰的前端是苦逼的:(嘈吐一地后,还是挤点时间总结一下WebComponent的内容吧,为以后作培训材料作点准备。

浮在水面上的痛

组件噪音太多了!

 在使用Bootstrap的Modal组件时,我们不免要Ctrl+c然后Ctrl+v下面一堆代码

<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body&hellip;</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->



 一个不留神误删了一个结束标签,或拼错了某个class或属性那就悲催了,此时一个语法高亮、提供语法检查的编辑器是如此重要啊!但是我其实只想配置个Modal而已。

 由于元素信息由标签标识符,元素特性树层级结构组成,所以排除噪音后提取的核心配置信息应该如下(YAML语法描述):

dialog:
modal: true
children:
header:
title: Modal title
closable: true
body:
children:
p:
textContent: One fine body&hellip;
footer
children:
button:
type: close
textContent: Close
button:
type: submit
textContent: Save changes

转换成HTML就是

<dialog modal>
<dialog-header title="Modal title" closable></dialog-header>
<dialog-body>
<p>One fine body&hellip;</p>
</dialog-body>
<dialog-footer>
<dialog-btn type="close">Close</dialog-btn>
<dialog-btn type="submit">Save changes</dialog-btn>
</dialog-footer>
</dialog>

而像Alert甚至可以极致到这样

<alert>是不是很简单啊?</alert>

可惜浏览器木有提供<alert></alert>,那怎么办呢?

手打牛丸模式1

既然浏览器木有提供,那我们自己手写一个吧!

<script>
'use strict'
class Alert{
constructor(el = document.createElement('ALERT')){
this.el = el
const raw = el.innerHTML
el.dataset.resolved = ''
el.innerHTML = `<div class="alert alert-warning alert-dismissible fade in">
<button type="button" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
${raw}
</div>`
el.querySelector('button.close').addEventListener('click', _ => this.close())
}
close(){
this.el.style.display = 'none'
}
show(){
this.el.style.display = 'block'
}
} function registerElement(tagName, ctorFactory){
[...document.querySelectorAll(`${tagName}:not([data-resolved])`)].forEach(ctorFactory)
}
function registerElements(ctorFactories){
for(let k in ctorFactories){
registerElement(k, ctorFactories[k])
}
}

清爽一下!

<alert>舒爽多了!</alert>
<script>
registerElements({alert: el => new Alert(el)})
</script>

复盘找问题

 虽然表面上实现了需求,但存在2个明显的缺陷

  1. 不完整的元素实例化方式

    原生元素有2种实例化方式

    a. 声明式
<!-- 由浏览器自动完成 元素实例化 和 添加到DOM树 两个步骤 -->
<input type="text">

b. 命令式

// 元素实例化
const input = new HTMLInputElement() // 或者 document.createElement('INPUT')
input.type = 'text'
// 添加到DOM树
document.querySelector('#mount-node').appendChild(input)

 由于声明式注重What to do,而命令式注重How to do,并且我们操作的是DOM,所以采用声明式的HTML标签比命令式的JavaScript会来得简洁平滑。但当我们需要动态实例化元素时,命令式则是最佳的选择。于是我们勉强可以这样

// 元素实例化
const myAlert = new Alert()
// 添加到DOM树
document.querySelector('#mount-node').appendChild(myAlert.el)
/*
由于Alert无法正常实现HTMLElement和Node接口,因此无法实现
document.querySelector('#mount-node').appendChild(myAlert)
myAlert和myAlert.el的差别在于前者的myAlert是元素本身,而后者则是元素句柄,其实没有明确哪种更好,只是原生方法都是支持操作元素本身,一下来个不一致的句柄不蒙才怪了
*/

 即使你能忍受上述的代码,那通过innerHTML实现半声明式的动态元素实例化,那又怎么玩呢?是再手动调用一下registerElement('alert', el => new Alert(el))吗?

 更别想通过document.createElement来创建自定义元素了。

2. 有生命无周期

 元素的生命从实例化那刻开始,然后经历如添加到DOM树、从DOM树移除等阶段,而想要更全面有效地管理元素的话,那么捕获各阶段并完成相应的处理则是唯一有效的途径了。

生命周期很重要

 当定义一个新元素时,有3件事件是必须考虑的:

  1. 元素自闭合: 元素自身信息的自包含,并且不受外部上下文环境的影响;
  2. 元素的生命周期: 通过监控元素的生命周期,从而实现不同阶段完成不同任务的目录;
  3. 元素间的数据交换: 采用property in, event out的方式与外部上下文环境通信,从而与其他元素进行通信。

     元素自闭合貌似无望了,下面我们试试监听元素的生命周期吧!

手打牛丸模式2

 通过constructor我们能监听元素的创建阶段,但后续的各个阶段呢?可幸的是可以通过MutationObserver监听document.body来实现:)

最终得到的如下版本:

'use strict'
class Alert{
constructor(el = document.createElement('ALERT')){
this.el = el
this.el.fireConnected = () => { this.connectedCallback && this.connectedCallback() }
this.el.fireDisconnected = () => { this.disconnectedCallback && this.disconnectedCallback() }
this.el.fireAttributeChanged = (attrName, oldVal, newVal) => { this.attributeChangedCallback && this.attributeChangedCallback(attrName, oldVal, newVal) } const raw = el.innerHTML
el.dataset.resolved = ''
el.innerHTML = `<div class="alert alert-warning alert-dismissible fade in">
<button type="button" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
${raw}
</div>`
el.querySelector('button.close').addEventListener('click', _ => this.close())
}
close(){
this.el.style.display = 'none'
}
show(){
this.el.style.display = 'block'
}
connectedCallback(){
console.log('connectedCallback')
}
disconnectedCallback(){
console.log('disconnectedCallback')
}
attributeChangedCallback(attrName, oldVal, newVal){
console.log('attributeChangedCallback')
}
} function registerElement(tagName, ctorFactory){
[...document.querySelectorAll(`${tagName}:not([data-resolved])`)].forEach(ctorFactory)
}
function registerElements(ctorFactories){
for(let k in ctorFactories){
registerElement(k, ctorFactories[k])
}
} const observer = new MutationObserver(records => {
records.forEach(record => {
if (record.addedNodes.length && record.target.hasAttribute('data-resolved')){
// connected
record.target.fireConnected()
}
else if (record.removedNodes.length){
// disconnected
const node = [...record.removedNodes].find(node => node.hasAttribute('data-resolved'))
node && node.fireDisconnected()
}
else if ('attributes' === record.type && record.target.hasAttribute('data-resolved')){
// attribute changed
record.target.fireAttributeChanged(record.attributeName, record.oldValue, record.target.getAttribute(record.attributeName))
}
})
})
observer.observe(document.body, {attributes: true, childList: true, subtree: true}) registerElement('alert', el => new Alert(el))

总结

 千辛万苦撸了个基本不可用的自定义元素模式,但通过代码我们进一步了解到对于自定义元素我们需要以下基本特性:

  1. 自定义元素可通过原有的方式实例化(<custom-element></custom-element>,new CustomElement()document.createElement('CUSTOM-ELEMENT'))
  2. 可通过原有的方法操作自定义元素实例(如document.body.appendChild等)
  3. 能监听元素的生命周期

    下一篇《WebComponent魔法堂:深究Custom Element 之 标准构建》中,我们将一同探究H5标准中Custom Element API,并利用它来实现满足上述特性的自定义元素:)

     尊重原创,转载请注明来自: http://www.cnblogs.com/fsjohnhuang/p/5918677.html _肥仔John

感谢

Custom ELement

Custom ELement v1

MutationObserver

WebComponent魔法堂:深究Custom Element 之 面向痛点编程的更多相关文章

  1. WebComponent魔法堂:深究Custom Element 之 标准构建

    前言  通过<WebComponent魔法堂:深究Custom Element 之 面向痛点编程>,我们明白到其实Custom Element并不是什么新东西,我们甚至可以在IE5.5上定 ...

  2. WebComponent魔法堂:深究Custom Element 之 从过去看现在

    前言  说起Custom Element那必然会想起那个相似而又以失败告终的HTML Component.HTML Component是在IE5开始引入的新技术,用于对原生元素作功能"增强& ...

  3. CSS魔法堂:深入理解line-height和vertical-align

    前言 一直听说line-height是指两行文本的基线间的距离,然后又说行高等于行距,最近还听说有个叫行间距的家伙,@张鑫旭还说line-height和vertical-align基情四射,贵圈真乱啊 ...

  4. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  5. CSS魔法堂:"那不是bug,是你不懂我!" by inline-block

    前言  每当来个需要既要水平排版又要设置固定高宽时,我就会想起display:inline-block,还有为了支持IE5.5/6/7的hack*display:inline;*zoom:1;.然后发 ...

  6. CSS魔法堂:小结一下Box Model与Positioning Scheme

    前言  对于Box Model和Positioning Scheme中3种定位模式的细节,已经通过以下几篇文章记录了我对其的理解和思考.  <CSS魔法堂:重新认识Box Model.IFC.B ...

  7. HTML5魔法堂:全面理解Drag & Drop API

    一.前言    在HTML4的时代,各前端工程师为了实现拖拽功能可说是煞费苦心,初听HTML5的DnD API觉得那些痛苦的日子将一去不复返,但事实又是怎样的呢?下面我们一起来看看DnD API的真面 ...

  8. CSS魔法堂:Absolute Positioning就这个样

    前言 当我们以position:absolute之名让元素脱离Normal flow的控制后,以为通过left和top属性值即可让元素得以无限的自由时,却发现还有各种神秘的力量左右着它的来去,于是我们 ...

  9. CSS魔法堂:你真的懂text-align吗?

    前言 也许提及text-align你会想起水平居中,但除了这个你对它还有多少了解呢?本篇打算和大家一起来跟text-align来一次负距离的交往,你准备好了吗? text-align属性详解 The ...

随机推荐

  1. redux-undo

    简介 通过包装reducer,创建一个state History,保留历史state,可以做退一步,进一步操作 1.install npm install --save redux-undo@beta ...

  2. Matlab slice方法和包络法绘制三维立体图

    前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...

  3. RestTemplate发送请求并携带header信息

    1.使用restTemplate的postForObject方法 注:目前没有发现发送携带header信息的getForObject方法. HttpHeaders headers = new Http ...

  4. 【项目管理】GitHub使用操作指南

    GitHub使用操作指南 作者:白宁超 2016年10月5日18:51:03> 摘要:GitHub的是版本控制和协作代码托管平台,它可以让你和其他人的项目从任何地方合作.相对于CVS和SVN的联 ...

  5. Java FtpClient 实现文件上传服务

    一.Ubuntu 安装 Vsftpd 服务 1.安装 sudo apt-get install vsftpd 2.添加用户(uftp) sudo useradd -d /home/uftp -s /b ...

  6. Android之三种网络请求解析数据(最佳案例)

    AsyncTask解析数据 AsyncTask主要用来更新UI线程,比较耗时的操作可以在AsyncTask中使用. AsyncTask是个抽象类,使用时需要继承这个类,然后调用execute()方法. ...

  7. github中的watch、star、fork的作用

    [转自:http://www.jianshu.com/p/6c366b53ea41] 在每个 github 项目的右上角,都有三个按钮,分别是 watch.star.fork,但是有些刚开始使用 gi ...

  8. Mono for Android—初体验之“电话拨号器”

    1.Main.axml文件: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmln ...

  9. 解决Windows 8.1中所有的应用(Modern App)无法打开(闪退)的问题

    我已经在3台电脑上遇到这个问题了,症状是,所有应用商店安装的App都无法打开,包括应用商店本身,在开始界面点击应用以后,应用的Logo一闪而过,然后就消失了,回到了开始界面.查看系统应用日志,会有这样 ...

  10. Lesson 16 A polite request

    Text If you park your car in the wrong place, a traffic policeman will soon find it. You will be ver ...