拓冰建站拓冰建站
首页 / 资讯中心 / 正文

十年了,您还不认识 WebComponent 吗?!

博主推荐前些天发现了一个巨牛的人工智能学习网站通俗易懂风趣幽默忍不住分享一下给大家。点击跳转到网站‍ 个人简介一个不甘平庸的平凡人️ Node专栏Node.js从入门到精通️ TS知识总结十万字TS知识点总结 你的一键三连是我更新的最大动力❤️ 欢迎私信博主加入前端交流群 目录前言1. Custom element2. Shadow DOM3. template4. slot结语前言MDNWeb Component 是一套不同的技术允许你创建可重用的定制元素它们的功能封装在你的代码之外并且在你的 web 应用中使用它们。简单的说Web Components 就是使用标准化的原生技术实现可重用的组件化开发模式Web Components 并不是新的概念该规范最早于 2011 年推出经多 10 多年的发展这套规范逐渐壮大并被主流浏览器所实现。在国内我们可能对它并不熟悉甚至会感到陌生日常开发中我们也很少会遇到它那它就不重要了吗回答这个问题之前先简单列举一些目前较为知名的 Web Components 组件库和框架以及基于此创建的网站应用程序Stencil一个专门用于构建 Web Components 的库可集成于 React、Vue 等 JavaScript 框架Github 11.8kLitGoogle 推出的用于构建 Web Components 的库同时提供响应式状态、作用域样式和声明式模板系统与 Stencil 相比更像是一个完整的 UI 框架Github 15.5kFAST微软推出的 Web Component 组件库同时提供了自定义 Web Components 的方法Github 8.5kYouTube打开控制台您会发现大量的自定义元素Photoshop网页版对您没看错这就是 Adobe 使用 Lit 构建的网页版 Photoshop 目前还处于 Beta 版本MSN微软使用基于 FAST 的 Web Components 重构了 MSN之前是 React 构建的…现在回答上面的问题Web Components 不重要吗我想说未必至少对于原生组件开发的方向它依然是目前唯一并且权威的解决方案Vue 官网文档明确指出其组件语法部分参考了 Web Components通过本文下面的介绍您也会发现 Vue 与原生 Web Components 的某些相似之处这足以说明这套规范并不是“空无主义”它的出现的确对 Web 开发的模式和流程产生了重要的影响。现如今我们为了复用组件、增强文档自定义能力我们在 Web 开发中大多都会使用一些 JavaScript 框架如Vue、 React 等。但随着 Web 技术的发展、Web Components 的完善与改进在未来的某一天我们真的有可能会在构建 UI 时将专注点从 JavaScript 上重新转移到原生 HTML 中目前Web Components 规范包含三项主要技术Custom element自定义元素一组 JavaScript API允许您自定义 HTML 标签及其行为。Shadow DOM影子 DOM一组 JavaScript API用于将封装的“影子”DOM 树附加到元素与主文档 DOM 分开呈现并控制其关联的功能。通过这种方式可以保持元素的功能私有这样它们就可以被脚本化和样式化而不用担心与文档的其他部分发生冲突。HTML template and slotsHTML 模板和插槽template和slot元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。1. Custom elementWeb Components 标准非常重要的一个特性是它使开发者能够将 HTML 页面的功能封装为 custom elements自定义元素而往常开发者不得不写一大堆冗长、深层嵌套的标签来实现同样的页面功能。创建一个自定义元素很简单整体分为两步通过类class来定义自定义元素的结构和内容。通过全局对象 customElements 里的 define 方法来注册自定义元素。先看一个简单的例子!DOCTYPEhtmlhtmllangenheadmetacharsetUTF-8metanameviewportcontentwidthdevice-width, initial-scale1.0titleCustomElements —— BakerChen/titlestyle.userInfo{color:rgb(103,103,232);}/style/headbody!-- 使用自定义元素可以像普通元素一样添加class --user-infonamebakerclassuserInfo/user-infoscript// 先创建一个继承与HTMLElement的类classUserInfoextendsHTMLElement{constructor(){// 必须在构造器中先调用一下 supersuper();// 使用attachShadow创建根元素constshadowthis.attachShadow({mode:open});// 创建各种元素constinfodocument.createElement(span);info.setAttribute(class,info);// 获取元素的name属性consttextthis.getAttribute(name);info.textContenttext;// 创建style标签用来存放样式conststyledocument.createElement(style);style.textContent.info { font-size: 80px; padding: 10px; background: #333; border-radius: 10px; };// 将元素添加到根元素中shadow.appendChild(style);shadow.appendChild(info);}}// 注册新的元素user-infoUserInfo为该元素的处理类customElements.define(user-info,UserInfo);/script/body/html效果上述代码中this.attachShadow({mode: open})的作用可以简单理解为创建一个容器如上图中的#shadow-rootopen在该容器内可以进行任何的 DOM 操作这其实是 Shadow DOM影子 DOM的 API所以下面再对其细说。因为自定义元素是使用类来定义所以它还可以实现继承JavaScript// 定义一个继承自原生 input 的元素classMyH1extendsHTMLInputElement{constructor(){super()// 定制一些效果this.value这是继承自原生input的自定义inputthis.style.minWidth200pxthis.style.padding5px 10pxthis.style.border1px solid redthis.style.borderRadius5px}}// 在使用 customElements.define 注册时传入第三个参数配置对象来指定这个元素是继承自 input 的customElements.define(my-h1,MyH1,{extends:input});HTML!-- 在使用继承原生标签的自定义元素时不是直接使用自定义元素 而是使用继承的原生标签然后通过 is 属性来指定自定义元素 --inputtypetextismy-h1效果自定义元素还具有自己的生命周期onnectedCallback当自定义元素首次被插入文档 DOM 时被调用。disconnectedCallback当自定义元素从文档 DOM 中删除时被调用。adoptedCallback当自定义元素被移动到新的文档时被调用。attributeChangedCallback: 当自定义元素增加、删除、修改自身属性时被调用即使元素未挂载。这里通过一个完整的例子演示JavaScript// 创建自定义元素classSquareextendsHTMLElement{// 如果需要在元素属性变化后触发attributeChangedCallback()回调函数必须使用observedAttributes() get 函数来监听这个属性。staticgetobservedAttributes(){// 监听c和l这两个属性当这俩属性改变时会触发attributeChangedCallback函数return[size,color];}constructor(){super();constshadowthis.attachShadow({mode:open});constdivdocument.createElement(div);conststyledocument.createElement(style);shadow.appendChild(style);shadow.appendChild(div);}// 定义生命周期connectedCallback(){console.log(元素首次被插入文档 DOM );constshadowthis.shadowRoot;// 拿到自定义元素的根容器 注意这一行// 元素首次挂载时先设置一下自己的尺寸通过设置style标签内容来实现shadow.querySelector(style).textContentdiv { width:${this.getAttribute(size)}px; height:${this.getAttribute(size)}px; background-color:${this.getAttribute(color)}; };}disconnectedCallback(){console.log(元素从文档 DOM 中删除);}adoptedCallback(){console.log(元素被移动到新的文档);}attributeChangedCallback(name,oldValue,newValue){console.log(元素增加、删除、修改自身属性);constshadowthis.shadowRoot;shadow.querySelector(style).textContentdiv { width:${this.getAttribute(size)}px; height:${this.getAttribute(size)}px; background-color:${this.getAttribute(color)}; };}}// 注册customElements.define(custom-square,Square);constadddocument.querySelector(.add);constpinkdocument.querySelector(.pink);constblackdocument.querySelector(.black);constremovedocument.querySelector(.remove);letsquaredocument.querySelector(custom-square);add.onclickfunction(){squaredocument.createElement(custom-square)// 初始的样式square.setAttribute(size,50);square.setAttribute(color,yellow)document.body.appendChild(square);};pink.onclickfunction(){square.setAttribute(size,200);square.setAttribute(color,pink);};black.onclickfunction(){square.setAttribute(size,100);square.setAttribute(color,black);};remove.onclickfunction(){square.remove()};HTMLdivbuttonclassaddadd/buttonbuttonclasspink200px pink/buttonbuttonclassblack100px black/buttonbuttonclassremoveremove/button/div效果需要注意的是点击 “add” 时会先运行两次setAttribute然后将自定义元素插入到了文档中所以控制台会打印元素增加、删除、修改自身属性元素增加、删除、修改自身属性元素首次被插入文档 DOM2. Shadow DOMShadow DOM 其实很简单它就是相当于一个独立的里面内容不会影响外部作用域的文档容器可以把它当作一个 div 来进行各种 DOM 操作比如添加子元素添加属性等等。MDN 上指出有一些 Shadow DOM 特有的术语Shadow host一个常规 DOM 节点Shadow DOM 会被附加到这个节点上。Shadow treeShadow DOM 内部的 DOM 树。Shadow boundaryShadow DOM 结束的地方也是常规 DOM 开始的地方。Shadow root: Shadow tree 的根节点。Shadow DOM 最常用的地方就是在自定义元素里上面介绍自定义元素时就是通过 Shadow DOM 的 Api 来创建容器的Element.attachShadow 方法可以创建一个 Shadow DOM 并把它挂载到 Element 元素下函数返回对 Shadow DOM 的引用。在使用Element.attachShadow方法时必须传入一个配置对象这个配置对象内必须具有一个 mode 属性该属性有两个值open 表示可以从 js 外部访问 shadow root 根节点例如上面自定义元素生命周期例子中表明需要注意的那一行jsconst shadow this.shadowRootclosed 拒绝从 js 外部访问关闭的 shadow root 根节点Element.shadowRoot目前还是一个实验性的方法它用来获取通过Element.attachShadow挂载的 Shadow DOM 。3.template原生的template标签里面的内容在加载页面时不会被呈现出来但这个标签本身会呈现在 DOM 树中从上图可以看到template里面的实际内容被包含进了一个文档碎片DocumentFragment中。当我们想要将实际内容展示到页面上时我们可以通过 JavaScript 来进行操作templateidmy-nameh1Baker/h1/templatebuttonidbtntest/buttonscriptconsttemplatedocument.getElementById(my-name);// template元素有个content属性该属性是只读的 DocumentFragment 包含了模板所表示的 DOM 树。consttemplateContenttemplate.content;document.body.appendChild(templateContent);constbtndocument.getElementById(btn);btn.onclickfunction(){console.log(template.content);}/script注意!上述代码中是使用appendChild将template的DocumentFragment插入到文档末尾。由于appendChild作用于DocumentFragment时会将DocumentFragment的全部内容转移不是复制到指定父节点的子节点列表中。所以当你点击test按钮时打印出的template的DocumentFragment内容为空如果你不想改动template的内容可以在使用appendChild之前对其克隆一份document.body.appendChild(templateContent.cloneNode(true));Vue与原生template的区别在 Vue 中有一个template内置元素这势必会导致与原生的template标签出现冲突并且 Vue2 与 Vue3 在模版解析时遇到template标签的行为还不太一样下面我们来看看Vue 中template内置元素 的行为与原生的刚好相反Vue 的template内置元素 里面的内容会被呈现只是template这个标签本身不会被呈现也就是说 Vue 中的template内置元素 只是一个虚拟容器的占位符。Vue2 默认会将模版中的template解析为 Vue 内置元素而不是原生的template标签那么我们如何在 Vue2 中使用原生的template标签呢方法稍微有点麻烦我们需要注册一个组件并自定义render渲染函数来手动创建原生的template标签templatedividapp!-- 使用注册的组件来应用原生的template标签 --my-component/my-component/div/templatescriptexportdefault{components:{MyComponent:{// 自定义渲染函数使用 createElement 创建原生的 template 标签render:function(createElement){returncreateElement(template,[createElement(h1,Baker)]);},},},};/script这样之后的效果就与原生使用template一致了然而在 Vue3 中就没有 Vue2 那么麻烦了因为 Vue3 与 Vue2 正好相反Vue3 默认会将模版中的template解析为原生的template标签而不是内置的template元素只有当template与v-if、v-else-if、v-else、v-for、v-slot中任一指令一起使用时Vue3 才会将template当作内置的template元素来处理。注意上面提到的 Vuetemplate内置元素并不包括 Vue 单文件组件中包裹整个模版的顶层template标签该顶层标签不是模板本身的一部分。4.slot原生的slot标签与 Vue 中的基础用法基本一致并且 MDN 上对其的中文解释也是参考了 Vue 的文档。有意识的是Vue 官方文档上说明“ Vue 组件的插槽机制是受原生 Web Componentslot元素的启发而诞生同时还做了一些功能拓展。”只能说是青出于蓝而胜于蓝了slot可以理解为一个占位内容当使用时可以对其进行定制化的替换这样就能满足一个通用组件在各种应用场景下的可定制性。可以为其设置一个name属性外界可以使用此name来选择性的替换对应的slot内容。单独使用它没有太大的意义因此常将它与template、Custom element、Shadow DOM 配合使用下面是一个完整的使用案例!-- 使用 template 创建一个模版 --templateiduser-info!-- 模版内样式 --style*{margin:0;padding:0;}.container{display:inline-block;border:1px solid #ccc;border-radius:10px;padding:10px;width:200px;height:80px;}.username{color:pink;}.userinfo{color:#626161;text-decoration:green wavy underline;}/style!-- 模版内结构 --divclasscontainerh1classusername!-- 使用 slot 具名插槽外部使用时可以通过 slotusername 来替换此内容 --slotnameusernameBaker/slot/h1pclassuserinfo!-- 使用 slot 默认插槽外部使用时自定义元素的内部元素没有指定slot的元素会替换此内容 --slot一个前端菜鸟/slot/p/div/template!-- 使用自定义元素 --user-info/user-infouser-info!-- 替换模版中的 slot nameusernameBaker/slot 内容 --spanslotusername张三/span!-- 替换模版中 slot一个前端菜鸟/slot 内容 --span一个大帅哥/span!-- 上面等价于 span slot一个大帅哥/span --/user-infouser-infospanslotusername李红/span一个小美女/user-infoscript// 创建自定义元素 user-info 的处理类classUserInfoextendsHTMLElement{constructor(){// 必须在构造器中先调用一下 supersuper();// 获取模版内容consttemplatedocument.getElementById(user-info);consttemplateContenttemplate.content;// 使用 影子DOM API attachShadow 创建自定义元素的容器constshadowRootthis.attachShadow({mode:open});// 将模版内容添加到容器当中shadowRoot.appendChild(templateContent.cloneNode(true));}}// 注册自定义元素user-infoUserInfo为该元素的处理类customElements.define(user-info,UserInfo);/script效果通过上面的这个例子可以明白将template与 Custom element、Shadow DOM 结合使用可以构建一个通用组件而引入slot可增加该组件的可定制性。与template不同的是Vue 在模版解析时遇到slot会根据使用场景及用户配置自动进行处理毕竟原生的和 Vue 的差距并不是很大结语通过前面的介绍您会发现 Web Component 是一套丰富的技术方案它通过 Custom element、Shadow DOM、template和slot的结合使用来在原生层面实现组件的封装与复用。组件化开发是一种趋势原生的支持为各种 UI 框架提供了标准化的思路和方向UI 框架的完善与扩展也反过来刺激了原生方案的发展。只是不知道原生的支持是否会持续快速的更新我们拭目以待参考2023 Web Components 现状译文Web Components 规范庞大且复杂本文所介绍的内容只是其中比较直观且易于理解的一部分内容如果大佬们意犹未尽可以去查阅官方规范也欢迎在评论区留言讨论。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门