WebComponent
WebComponent
前言
最近加入到新项目组负责前端技术预研和选型,一直偏向于以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">×</span></button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</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…
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…</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">×</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个明显的缺陷
- 不完整的元素实例化方式
原生元素有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来创建自定义元素了。
- 有生命无周期
元素的生命从实例化那刻开始,然后经历如添加到DOM树、从DOM树移除等阶段,而想要更全面有效地管理元素的话,那么捕获各阶段并完成相应的处理则是唯一有效的途径了。
生命周期很重要
当定义一个新元素时,有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">×</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))
总结
千辛万苦撸了个基本不可用的自定义元素模式,但通过代码我们进一步了解到对于自定义元素我们需要以下基本特性:
- 自定义元素可通过原有的方式实例化(
<custom-element></custom-element>,new CustomElement()和document.createElement('CUSTOM-ELEMENT')) - 可通过原有的方法操作自定义元素实例(如
document.body.appendChild等) - 能监听元素的生命周期
下一篇《WebComponent魔法堂:深究Custom Element 之 标准构建》中,我们将一同探究H5标准中Custom Element API,并利用它来实现满足上述特性的自定义元素:)
尊重原创,转载请注明来自: http://www.cnblogs.com/fsjohnhuang/p/5918677.html ^_^肥仔John
感谢
如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!

WebComponent的更多相关文章
- WebComponent魔法堂:深究Custom Element 之 从过去看现在
前言 说起Custom Element那必然会想起那个相似而又以失败告终的HTML Component.HTML Component是在IE5开始引入的新技术,用于对原生元素作功能"增强& ...
- WebComponent魔法堂:深究Custom Element 之 标准构建
前言 通过<WebComponent魔法堂:深究Custom Element 之 面向痛点编程>,我们明白到其实Custom Element并不是什么新东西,我们甚至可以在IE5.5上定 ...
- WebComponent魔法堂:深究Custom Element 之 面向痛点编程
前言 最近加入到新项目组负责前端技术预研和选型,一直偏向于以Polymer为代表的WebComponent技术线,于是查阅各类资料想说服老大向这方面靠,最后得到的结果是:"资料99%是英语 ...
- 使用web-component搭建企业级组件库
组件库的现状 前端目前比较主流的框架有react,vuejs,angular等. 我们通常去搭建组件库的时候都是基于某一种框架去搭建,比如ant-design是基于react搭建的UI组件库,而ele ...
- 在angular项目中使用web-component ----How to use Web Components with Angular
原文: https://medium.com/@jorgecasar/how-to-use-web-components-with-angular-41412f0bced8 ------------- ...
- 转: GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean
十年前,Martin Fowler撰写了 GUI Architectures 一文,至今被奉为经典.本文所谈的所谓架构二字,核心即是对于对于富客户端的 代码组织/职责划分 .纵览这十年内的架构模式变迁 ...
- 通过全局getApp获取全局实例获取数据
学习是每一个人都要面对的铁一般的事实,不进则退.学习同样讲究途径和方法,面对知识这个巨人,我们永远不会有成年的那一刻,但我们可以让自己毕生尽可能地吸取更多有价值的信息,好让自己人生充满各种“意义”存在 ...
- Angular指令1
Angular的指令 也就是directive,其实就是一WebComponent,以前端的眼光来看,好象很复杂,但是以后端的眼光来看,还是非常简单的.其实就是一个中等水平的类. var myModu ...
- Metronic 与 VS2013/2015 合作开发
Metronic 与 VS2013/2015 合作开发 去年购买了一个:METRONIC (http://www.keenthemes.com/) ,最近下了最新的版本:V3.7 ,解压缩后,目录 ...
随机推荐
- 阿里技术保障-KeepAlive
http://blog.sina.cn/dpool/blog/s/blog_e59371cc0102ux5w.html?wm=3049_a111
- Unicode编码及其实现:UTF-16、UTF-8,and more
http://blog.csdn.net/thl789/article/details/7506133
- linux 清空文件内容命令
清空文件内容命令 $ echo "" >log.log > 是重写,覆盖式 >>是尾部追加
- find——文件查找命令 linux一些常用命令
find 命令eg: 一般文件查找方法: 1. find /home -name file , 在/home目录下查找文件名为file的文件2. find /home -name '*file ...
- Java基础知识强化之IO流笔记36:InputStreamReader/OutputStreamWriter 复制文本文件案例
1. 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中. 数据源: a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader 目的地: b.t ...
- [Form Builder]Form中的validate验证事件
转:http://yedward.net/?id=70 Form的validate行为可以由一个总的form级别的validation属性来控制,可以通过set_form_property来设置成PR ...
- 20151211jquery ajax进阶代码备份
//数据处理 $('form input[type=button]').click(function() { //json处理 /*$.ajax({ type:'POST', url:'test.js ...
- SqlServer2008误操作数据(delete或者update)后恢复数据
实际工作中,有时会直接在数据库中操作数据,比如对数据进行delete或者update操作,当进行这些操作的时候,如果没有加上where条件或者where条件不合理,那么导致的结果可想而知,如果操作的又 ...
- ASP.NET页面生命周期总结(2)
HttpAplicationFactory获取一个HttpApplication对象: 内部:1.如果是第一次请求过来,那么就把global文件编译成一个类型.(后续请求来的,就可以直接获取这个类型) ...
- IAP (In-App Purchase)中文文档
内容转自:http://yarin.blog.51cto.com/1130898/549141 一.In App Purchase概览 Store Kit代表App和App Store之间进行通信.程 ...