WebComponent 是官方定义的自定义组件实现方式,它可以让开发者不依赖任何第三方框架(如Vue,React)来实现自定义页面组件;达到组件复用效果
一个简单例子,让页面显示 hello world:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< 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 及其行为,然后可以在您的用户界面中按照需要使用它们
分为两种形式:
自主定制元素:是独立的元素,它不继承其他内建的 HTML 元素,可以直接把它们写成 HTML 标签的形式,来在页面上使用,例如我们刚才自定义的 <my-text>
自定义内置元素:继承自内置的 HTML 元素。指定所需扩展的元素
- 使用时需通过
is
属性指定custom element
的名称,必须包含一个短横线 - 注册的时候必须使用
extends
的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!-- 自定义内置元素 使用 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
生命周期函数,处理节点操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<!-- 自主定制元素--> < body > < my-text /> < script > class MyText extends HTMLElement { constructor() { super(); } connectedCallback() { this.append("hello world"); } } window.customElements.define("my-text", MyText); </ script > </ body > |
生命周期函数
connectedCallback
:插入文档时,可能被多次触发,比如删除后又添加到文档
disconnectedCallback
:从文档删除时,可配置做清理工作
adoptedCallback
:被移动新文档时
attributeChangedCallback
:属性变化时
- 配合
observedAttributess
属性一起使用,指定监听的属性 - 使用
setAttribute
方法更新属性
不同操作触发的生命周期函数:
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
< 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 模板字串符的方式创建模板,提示不友好,复用性差
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
< body > < product-item name = "关东煮" 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 = ` < img class = "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 > </ body > |
template 方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
< body > <!-- template --> < template id = "tpl-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 > </ template > < product-item name = "关东煮" 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 > </ body > |
slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
< 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,其内部样式不共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
< 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 节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
< 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 > |
引入外部样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
< body > < template id = "tpl" > <!-- 方式一: --> < link rel = "stylesheet" href = "index.css" rel = "external nofollow" /> < 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 组件例子
- 通过创建 商品 组件,并使得点击能跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
< 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 > |
原文链接:https://juejin.cn/post/7203351367313489957
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容