今天的前端开发基本上离不开React和Vue这两个框架的支持,并且从这两个框架衍生出很多自定义组件库:Element(Vue)的出现AntDesign(React)等组件库让我们可以直接使用封装好的组件,并且在开源社区的帮助下,已经出现了很多模板项目(vue-element-admin,AntDesignPro),可以让我们快速上手一个专案。React和Vue虽然为我们的组件开发提供了便利,但是在组件开发思路上,一是自创的JSX语法,二是特有的单文件模板语法。两者的目标都是为组件提供一种封装方式。毕竟里面有原创的东西,和我们刚开始接触的Web基础的HTML、CSS、JS方法还是有一些区别的。今天要介绍的是通过HTML、CSS、JS实现自定义组件,也是目前浏览器原生提供的解决方案:WebComponents。什么是网络组件?WebComponents本身并不是一个单一的规范,而是一组DOMAPI和HTML规范,用于创建具有自定义名称的可重用HTML标签,并可以直接在您的Web应用程序中使用。代码重用一直是我们的目标。我们可以将JS中可复用的代码封装成一个函数,但是对于复杂的HTML(包括相关的样式和交互逻辑),我们一直没有更好的复用方式。要么使用后端模板引擎,要么使用现有的框架重新封装DOMAPI,而WebComponents的出现就是为了补充浏览器在这方面的能力。如何使用Web组件?WebComponents中包含的几个规范已经在W3C和HTML标准中进行了标准化,主要由三部分组成:自定义元素(customelements):一组用于创建自定义HTML标签的JavaScriptAPI,并允许在创建标签时进行一些操作或销毁;ShadowDOM(影子DOM):一组JavaScriptAPI,用于将创建的DOMTree插入到现有元素中,DOMTree不能被外部修改。不用担心元素会受到其他地方的影响;HTML模板(HTMLtemplates):通过、 标签,其文本内容为:HelloShenfq。这种形式的自定义元素称为:Autonomouscustomelements,是一种可以直接在HTML中使用的独立元素。扩展现有的HTML标签除了定义新的HTML标签外,我们还可以扩展现有的HTML标签。比如我们需要封装一个类似标签能力的组件,可以使用如下方法:if(this.hasAttribute("skills")&&this.getAttribute("skills").includes(',')){//读取skills属性的值constskills=this.getAttribute("skills").split(',');skills.forEach(skill=>{constitem=document.createElement("li");item.innerText=skill;this.appendChild(item);})}}}//扩展
标签customElements.define("技能列表",SkillList,{extends:"ul"});
扩展已有的标签,需要用到customElements.define方法的第三个参数,第二个参数的class也是需要继承相应的需要扩展标签的类。使用时只需要在标签中添加is属性,属性值为第一个参数定义的名称。生命周期自定义元素的生命周期比较简单,只提供了四种回调方法:connectedCallback:当自定义元素被插入到页面的DOM文档中时调用。disconnectedCallback:当自定义元素从DOM文档中移除时调用。adoptedCallback:移动自定义元素时调用。attributeChangedCallback:当自定义元素添加、删除或修改其自身的属性时调用。下面演示如何使用:classHelloUserextendsHTMLElement{constructor(){//必须调用super方法super();//创建div标签const$box=document.createElement("p");letuserName="UserName";if(this.hasAttribute("name")){//如果有name属性,读取name属性的值userName=this.getAttribute("name");}//设置div的文本内容tag$box.innerText=`Hello${userName}`;//创建影子节点,其他创建的元素要附加到这个节点上constshadow=this.attachShadow({mode:"open"});shadow.appendChild($box);}connectedCallback(){console.log('createelement')//移动元素到iframesetTimeout(()=>{constiframe=document.getElementsByTagName("iframe")[0]iframe.contentWindow.document.adoptNode(this)},5e3)after5s}disconnectedCallback(){console.log('deleteelement')}adoptedCallback(){console.log('moveelement')}}
