资讯专栏INFORMATION COLUMN

WebComponent魔法堂:深究Custom Element 之 面向痛点编程

flyer_dev / 2256人阅读

摘要:前言最近加入到新项目组负责前端技术预研和选型,一直偏向于以为代表的技术线,于是查阅各类资料想说服老大向这方面靠,最后得到的结果是资料是英语无所谓,最重要是上符合要求,技术的事你说了算。但当我们需要动态实例化元素时,命令式则是最佳的选择。

前言

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

浮在水面上的痛 组件噪音太多了!

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


 一个不留神误删了一个结束标签,或拼错了某个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就是


  
  
    

One fine body…

Close Save changes

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

是不是很简单啊?

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

手打牛丸模式1

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

复盘找问题

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

不完整的元素实例化方式
原生元素有2种实例化方式

声明式


命令式

// 元素实例化
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 = `
${raw}
` 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))
总结

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

自定义元素可通过原有的方式实例化(,new CustomElement()document.createElement("CUSTOM-ELEMENT"))

可通过原有的方法操作自定义元素实例(如document.body.appendChild等)

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

 尊重原创,转载请注明来自: http://www.cnblogs.com/fsjohn... ^_^肥仔John

感谢

Custom ELement
Custom ELement v1
MutationObserver

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80535.html

相关文章

  • WebComponent魔法:深究Custom Element 标准构建

    摘要:明确各阶段适合的操作用于初始化元素的状态和设置事件监听,或者创建。事件类型转换通过捕获事件,然后通过发起事件来对事件类型进行转换,从而触发更符合元素特征的事件类型。 前言  通过《WebComponent魔法堂:深究Custom Element 之 面向痛点编程》,我们明白到其实Custom Element并不是什么新东西,我们甚至可以在IE5.5上定义自己的alert元素。但这种简单...

    philadelphia 评论0 收藏0
  • JS魔法深究JS异步编程模型

    摘要:而同步和异步则是描述另一个方面。异步将数据从内核空间拷贝到用户空间的操作由系统自动处理,然后通知应用程序直接使用数据即可。 前言  上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出这条粉肠到底在说啥?的结果:(下面是PPT的讲义,具体的PPT和示例代码在https://github.com/fsjohnhuan...上,有兴趣就上去看看吧! ...

    idealcn 评论0 收藏0
  • CSS魔法:小结一下Box Model与Positioning Scheme

    摘要:魔法堂重新认识和魔法堂你一定误解过的魔法堂就这个样魔法堂说说那个被埋没的志向深入细节后会发现中定位模式之间,和之间存在千丝万缕的关系,必须以俯瞰的角度捋一下。当采用时,属性的实际值会被重置为。由于和则需要通过来引入来提供盒子定位微调的功能。 前言  对于Box Model和Positioning Scheme中3种定位模式的细节,已经通过以下几篇文章记录了我对其的理解和思考。 《CSS...

    szysky 评论0 收藏0
  • CSS魔法:你真的理解z-index吗?

    摘要:与的映射关系为。与根对应的对应的层叠上下文,是其他的祖先,的范围覆盖整条。注意的默认值为,自动赋值为。对于,它会将赋予给对应的,而则不会。 一、前言                                假如只是开发简单的弹窗效果,懂得通过z-index来调整元素间的层叠关系就够了。但要将多个弹窗间层叠关系给处理好,那么充分理解z-index背后的原理及兼容性问题就是必要的知识...

    andycall 评论0 收藏0
  • CSS魔法:display:none与visibility:hidden的恩怨情仇

    摘要:不耽误表单提交数据虽然我们无法看到的元素,但当表单提交时依然会将隐藏的元素的值提交上去。让元素在见面上不可视,但保留元素原来占有的位置。不过由于各浏览器实现效果均有出入,因此一般不会使用这个值。继承父元素的值。 前言  还记得面试时被问起请说说display:none和visibility:hidden的区别吗?是不是回答完display:none不占用原来的位置,而visibilit...

    selfimpr 评论0 收藏0

发表评论

0条评论

flyer_dev

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<