2023年了,你应该了解下 WebComponent

WebComponent 是官方定义的自定义组件实现方式,它可以让开发者不依赖任何第三方框架(如Vue,React)来实现自定义页面组件;达到组件复用效果

一个简单例子,让页面显示 hello world:

<body><!-- 使用组件的方式 --><my-text /><script> class MyText extends HTMLElement {constructor() {super();this.append("hello world");}}window.customElements.define("my-text", MyText); </script>
</body> 

三项主要技术

1、Custom elements (自定义元素)

  • 一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们

分为两种形式:

1.自主定制元素:是独立的元素,它不继承其他内建的 HTML 元素,可以直接把它们写成 HTML 标签的形式,来在页面上使用,例如我们刚才自定义的 <my-text>
2.自定义内置元素:继承自内置的 HTML 元素。指定所需扩展的元素

1.1.使用时需通过 is 属性指定 custom element 的名称,必须包含一个短横线2.注册的时候必须使用 extends 的属性

<!-- 自定义内置元素 使用 is-->
<body><!-- 使用组件的方式 --><p is="color-p" color="green">云牧</p><script> class ColorP extends HTMLParagraphElement {constructor() {super();this.style.color = this.getAttribute("color");}}window.customElements.define("color-p", ColorP, { extends: "p" }); </script>
</body> 

推荐在 connectedCallback 生命周期函数,处理节点操作

<!-- 自主定制元素-->
<body><my-text /><script> class MyText extends HTMLElement {constructor() {super();}connectedCallback() {this.append("hello world");}}window.customElements.define("my-text", MyText); </script>
</body> 

生命周期函数

1.connectedCallback:插入文档时,可能被多次触发,比如删除后又添加到文档
2.disconnectedCallback:从文档删除时,可配置做清理工作
3.adoptedCallback:被移动新文档时
4.attributeChangedCallback:属性变化时

1.1.配合 observedAttributess 属性一起使用,指定监听的属性2.使用 setAttribute 方法更新属性

不同操作触发的生命周期函数:

例子:

<body><div id="container"><p is="my-text" text="云牧" id="myText"></p></div><button id="btnUpdateText">更新属性</button><button id="btnRemove">删除节点</button><button id="btnRestore">恢复节点</button><button id="btnAdopt">移动节点</button><iframe src="./ifr.html" id="ifr"></iframe><script> class MyText extends HTMLParagraphElement {constructor() {super();}connectedCallback() {console.log("生命周期:connectedCallback");this.append("你好:" + this.getAttribute("text"));}disconnectedCallback() {console.log("生命周期:disconnectedCallback");this.innerHTML = "";}// 监测的属性static get observedAttributes() {return ["text"];}attributeChangedCallback(name, oldValue, newValue) {console.log("生命周期:attributeChangedCallback", name, oldValue, newValue);// 最先触发是此函数,判断是不是第一次触发,第一次的话,只由 connectedCallback 处理if (oldValue != null) {this.replaceChildren("你好:" + newValue);}}adoptedCallback() {console.log("生命周期:adoptedCallback");}}window.customElements.define("my-text", MyText, { extends: "p" });const myText = document.getElementById("myText");btnUpdateText.addEventListener("click", function (e) {myText.setAttribute("text", "黛玉");});btnRemove.addEventListener("click", function (e) {myText.remove();});btnRestore.addEventListener("click", function (e) {container.appendChild(myText);});btnAdopt.addEventListener("click", () => {const textNode = ifr.contentWindow.document.getElementById("myText");container.appendChild(document.adoptNode(textNode));}); </script>
</body> 

2、HTML templates(HTML 模板)

  • 使用 JS 模板字串符的方式创建模板,提示不友好,复用性差
<body><product-itemname="关东煮"img="//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg<img src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" /><div class="name"></div><div class="price"></div>`;this.innerHTML = content;this.querySelector(".img").src = this.getAttribute("img");this.querySelector(".name").innerText = this.getAttribute("name");this.querySelector(".price").innerText = this.getAttribute("price");}}window.customElements.define("product-item", ProductItem); </script" style="margin: auto" />
</body> 

template 方式

<body><<img src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" /><div class="name"></div><div class="price"></div></template><product-itemname="关东煮"img="//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg!cc_200x200.webp"price="49.8"></product-item><script> class ProductItem extends HTMLElement {constructor() {super();}connectedCallback() {const content = document.getElementById("tpl-product-item").content.cloneNode(true);// 插入克隆的模板内容this.append(content);this.querySelector(".img").src = this.getAttribute("img");this.querySelector(".name").innerText = this.getAttribute("name");this.querySelector(".price").innerText = this.getAttribute("price");}}window.customElements.define("product-item", ProductItem); </script" style="margin: auto" />
</body> 

slot

<body><template id="tpl-test"><style> .title {color: green;} </style><div class="title">标题</div><slot name="slot-des">默认内容</slot></template><test-item><div slot="slot-des">不是默认内容</div></test-item><script> class TestItem extends HTMLElement {constructor() {super();}connectedCallback() {const content = document.getElementById("tpl-test").content.cloneNode(true);const shadow = this.attachShadow({ mode: "open" });shadow.append(content);}}window.customElements.define("test-item", TestItem); </script>
</body> 

3、Shadow DOM(影子 DOM)

影子DOM,其内部样式不共享

<body><!--不受外部 .container.container 的颜色影响 --><my-item-s></my-item-s><div class="container">My item</div><style> .container.container {color: green;} </style><template id="tpl"><style> .container {color: pink;} </style><div class="container">My Item</div></template><script> class MyItemShadow extends HTMLElement {constructor() {super();}connectedCallback() {const content = document.getElementById("tpl").content.cloneNode(true);const shadow = this.attachShadow({ mode: "open" });shadow.append(content);}}window.customElements.define("my-item-s", MyItemShadow); </script>
</body> 

影子DOM,其内部元素不可以直接被访问到

有一个重要的参数 mode

  • open: shadow root 元素通过 js 从外部访问根节点
  • closed:拒绝 js 从外部访问关闭的 shadow root 节点
<body><template id="tpl"><div class="title"></div><div class="des"></div></template><note-item class="note-item" title="标题" des="内容"></note-item><script> class NoteItem extends HTMLElement {constructor() {super();}connectedCallback() {const content = document.getElementById("tpl").content.cloneNode(true);const shadow = this.attachShadow({ mode: "open" });shadow.append(content);// 如果是 open 则可以继续访问操作内部 dom// console.log(document.querySelector(".note-item").shadowRoot.querySelector(".title"));shadow.querySelector(".title").textContent = this.getAttribute("title");shadow.querySelector(".des").textContent = this.getAttribute("des");}}window.customElements.define("note-item", NoteItem); </script>
</body> 

引入外部样式:

<body><template id="tpl"><!-- 方式一: --><link rel="stylesheet" href="index.css" /><div>My Item</div></template><my-item></my-item><script> class MyItem extends HTMLElement {constructor() {super();}connectedCallback() {const content = document.getElementById("tpl").content.cloneNode(true);const shadow = this.attachShadow({ mode: "open" });shadow.append(content);// 方式二:const linkEl = document.createElement("link");linkEl.setAttribute("rel", "stylesheet");linkEl.setAttribute("href", "index.css");shadow.appendChild(linkEl);}}window.customElements.define("my-item", MyItem); </script>
</body> 

动态创建 webComponent 组件例子

  • 通过创建 商品 组件,并使得点击能跳转
<body><div id="product-list" style="display: flex"></div><template id="product-item"><style> .product-item {margin-left: 15px;cursor: pointer;}.img {width: 100px;}.name {text-align: center;}.price {color: #999;text-align: center;} </style><div class="product-item"><img class="img" src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" /><div class="name"></div><div class="price"></div></div></template><script> class ProductItemElement extends HTMLElement {constructor(props) {super(props);this.addEventListener("click", () => {window.open(`https://item.jd.com/${this.id}.html`);});}connectedCallback() {const shadow = this.attachShadow({ mode: "open" });const content = document.getElementById("product-item").content.cloneNode(true);content.querySelector(".img").src = this.img;content.querySelector(".name").innerText = this.name;content.querySelector(".price").innerText = this.price;shadow.appendChild(content);}}window.customElements.define("product-item", ProductItemElement); </script><script> const products = [{name: "关东煮",img: "//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg!cc_200x200.webp",id: "10026249568453",price: 49.8},{name: "土鸡蛋",img: "//img11.360buyimg.com/seckillcms/s200x200_jfs/t1/172777/32/27438/130981/61fbd2e0E236000e0/7f5284367e2f5da6.jpg!cc_200x200.webp",id: "10024773802639",price: 49.8},{name: "东北蜜枣粽子",img: "//img20.360buyimg.com/seckillcms/s200x200_jfs/t1/129546/31/19459/110768/60b1f4b4Efd47366c/3a5b80c5193bc6ce.jpg!cc_200x200.webp",id: "10035808728318",price: 15}];const productList = document.getElementById("product-list");const elList = products.map(product => {// 创建组件const el = document.createElement("product-item");el.img = product.img;el.name = product.name;el.price = product.price;el.id = product.id;return el;});productList.append.apply(productList, elList); </script>
</body> 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

猜你喜欢

转载自blog.csdn.net/Android062005/article/details/129447474