博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebComponent魔法堂:深究Custom Element 之 面向痛点编程
阅读量:6000 次
发布时间:2019-06-20

本文共 4910 字,大约阅读时间需要 16 分钟。

前言

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

浮在水面上的痛

组件噪音太多了!

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

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就是

One fine body…

Close
Save changes

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

是不是很简单啊?

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

手打牛丸模式1

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

清爽一下!

舒爽多了!

复盘找问题

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

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

a. 声明式

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来创建自定义元素了。

  1. 有生命无周期
     元素的生命从实例化那刻开始,然后经历如添加到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 = `
${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))

总结

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

  1. 自定义元素可通过原有的方式实例化(<custom-element></custom-element>,new CustomElement()document.createElement('CUSTOM-ELEMENT'))
  2. 可通过原有的方法操作自定义元素实例(如document.body.appendChild等)
  3. 能监听元素的生命周期
    下一篇《WebComponent魔法堂:深究Custom Element 之 标准构建》中,我们将一同探究H5标准中Custom Element API,并利用它来实现满足上述特性的自定义元素:)
     尊重原创,转载请注明来自: ^_^肥仔John

感谢

你可能感兴趣的文章
PHP与JSP的比较
查看>>
ASP.NET MVC4使用JCrop裁剪图片并上传
查看>>
Forget Java to learn Javascript from 0.--Day 1
查看>>
centos7使用haproxy1.7.5实现反向代理负载均衡实战
查看>>
jQuery- 表单验证插件-Validation
查看>>
java调用RestFul WebService的例子
查看>>
笔记《javascript高级程序设计》 第12章 DOM2和DOM3
查看>>
android Fragment的数据传递
查看>>
PC平台在Unity3D中播放硬盘ogg,mp3,wav文件
查看>>
RandomAccessFile
查看>>
leetcode160
查看>>
ARM:移动GPU往PC GPU效能迈进
查看>>
js触屏事件
查看>>
[转]Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析
查看>>
编程范式(Programming Paradigm)-[ 程序员的编程世界观 ]
查看>>
sublime text 3 3017 系统默认快捷键定义文件Default (Windows).sublime-keymap
查看>>
windows server2012部署Cognos问题小结
查看>>
在 Domino 系统中如何路由邮件
查看>>
jsp内置对象,Response对象??
查看>>
每日一句
查看>>