网站首页 > 技术文章 正文
此文由掘金@hpstream_授权发布
https://juejin.cn/post/7034796986889043999
谈到WebComponent 很多人很容易想到Vue,React中的组件。但其实H5原生也已经支持了组件的编写。
关于Web Components,MDN是如此定义其概念和使用的:
Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。
作为开发者,我们都知道尽可能多的重用代码是一个好主意。这对于自定义标记结构来说通常不是那么容易 — 想想复杂的HTML(以及相关的样式和脚本),有时您不得不写代码来呈现自定义UI控件,并且如果您不小心的话,多次使用它们会使您的页面变得一团糟。
Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。
?
Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
?
?
Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
?
?
HTML templates(HTML模板):template 和 slot 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
?
上面的概念难以理解,我们通过一个例子看下如何编写一个组件
案例一
「1、什么是 HTML templates(HTML模板)?」
<template id="btn">
<button class="hp-button">
<slot></slot>
</button>
</template>
「2、Custom elements(自定义元素)」
class HpButton extends HTMLElement {
constructor() {
super();
//...
}
}
// 定义了一个自定义标签 组件
window.customElements.define('hp-button', HpButton)
「3、Shadow DOM(影子DOM)」
let shadow = this.attachShadow({
mode: 'open'
});
let btnTmpl = document.getElementById('btn');
let cloneTemplate = btnTmpl.content.cloneNode(true)
const style = document.createElement('style');
let type = this.getAttribute('type') || 'default';
const btnList = {
'primary': {
background: '#ff0000',
color: '#fff'
},
'default': {
background: '#909399',
color: '#fff'
}
}
style.textContent = `
.hp-button{
outline:none;
border:none;
border-radius:4px;
padding:5px 20px;
display:inline-flex;
background:${btnList[type].background};
color:${btnList[type].color};
cursor:pointer
}
`
// dom操作具备移动型
shadow.appendChild(style)
shadow.appendChild(cloneTemplate)
一个完整简单的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
:root {
--background-color: black;
--text-color: yellow
}
</style>
<hp-button type="primary">
<input type="text">
按钮
</hp-button>
<hp-button>前端晚间课</hp-button>
<!-- 内容是不会被渲染到视图上,不会影响页面展示,可以使用模板 -->
<template id="btn">
<button class="hp-button">
<slot></slot>
</button>
</template>
<script>
class HpButton extends HTMLElement {
constructor() {
super();
let shadow = this.attachShadow({
mode: 'open'
});
let btnTmpl = document.getElementById('btn');
let cloneTemplate = btnTmpl.content.cloneNode(true)
const style = document.createElement('style');
let type = this.getAttribute('type') || 'default';
const btnList = {
'primary': {
background: '#ff0000',
color: '#fff'
},
'default': {
background: '#909399',
color: '#fff'
}
}
style.textContent = `
.hp-button{
outline:none;
border:none;
border-radius:4px;
padding:5px 20px;
display:inline-flex;
background:${btnList[type].background};
color:${btnList[type].color};
cursor:pointer
}
`
// dom操作具备移动型
shadow.appendChild(style)
shadow.appendChild(cloneTemplate)
}
}
// 定义了一个自定义标签 组件
window.customElements.define('hp-button', HpButton)
</script>
</body>
</html>
结论
原生组件与Vue,React的组件的概念是相似的,但是从写法上来看有区别。
深入学习
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
- connectedCallback:当 custom element首次被插入文档DOM时,被调用。
- disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
- adoptedCallback:当 custom element被移动到新的文档时,被调用。
- attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。
我们来看一下它们的一下用法示例。下面的代码出自life-cycle-callbacks(https://github.com/mdn/web-components-examples/tree/master/life-cycle-callbacks)示例(查看在线示例:https://mdn.github.io/web-components-examples/life-cycle-callbacks/)。这个简单示例只是生成特定大小、颜色的方块。custom element看起来像下面这样:
生命周期的代码的具体示例:
class Square extends HTMLElement {
// Specify observed attributes so that
// attributeChangedCallback will work
static get observedAttributes() {
return ['c', 'l'];
}
constructor() {
// Always call super first in constructor
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
const style = document.createElement('style');
shadow.appendChild(style);
shadow.appendChild(div);
}
connectedCallback() {
console.log('Custom square element added to page.');
updateStyle(this);
}
disconnectedCallback() {
console.log('Custom square element removed from page.');
}
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log('Custom square element attributes changed.');
updateStyle(this);
}
}
customElements.define('custom-square', Square);
事件
document.querySelector('???').dispatchEvent(new CustomEvent('changeName', {
detail: {
name: 1111,
}
}))
折叠面板的案例
- 完成模版部分的定义:
<!-- 没有实际意义, 不会渲染到页面上 -->
<template id="collapse_tmpl">
<div class="zf-collapse">
<slot></slot>
</div>
</template>
<template id="collapse_item_tmpl">
<div class="zf-collapse-item">
<div class="title"></div>
<div class="content">
<slot></slot>
</div>
</div>
</template>
- 创建组件:
class Collapse extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: 'open'
});
const tmpl = document.getElementById('collapse_tmpl');
let cloneTemplate = tmpl.content.cloneNode(true);
let style = document.createElement('style');
// :host 代表的是影子的根元素
style.textContent = `
:host{
display:flex;
border:3px solid #ebebeb;
border-radius:5px;
width:100%;
}
.zf-collapse{
width:100%;
}
`
shadow.appendChild(style);
shadow.appendChild(cloneTemplate);
let slot = shadow.querySelector('slot'); // 监控slot变化
slot.addEventListener('slotchange', (e) => {
this.slotList = e.target.assignedElements();
this.render();
})
}
static get observedAttributes() { // 监控属性的变化
return ['active']
}
// update
attributeChangedCallback(key, oldVal, newVal) {
if (key == 'active') {
this.activeList = JSON.parse(newVal);
this.render();
}
}
render() {
if (this.slotList && this.activeList) {
[...this.slotList].forEach(child => {
child.setAttribute('active', JSON.stringify(this.activeList))
});
}
}
}
export default Collapse
class CollapseItem extends HTMLElement {
constructor() {
super();
let shadow = this.attachShadow({
mode: 'open'
});
let tmpl = document.getElementById('collapse_item_tmpl');
let cloneTemplate = tmpl.content.cloneNode(true);
let style = document.createElement('style');
this.isShow = true; // 标识自己是否需要显示
style.textContent = `
:host{
width:100%;
}
.title{
background:#f1f1f1;
line-height:35px;
height:35px;
}
.content{
font-size:14px;
}
`
shadow.appendChild(style)
shadow.appendChild(cloneTemplate);
this.titleEle = shadow.querySelector('.title');
this.titleEle.addEventListener('click', () => {
// 如果将结果传递给父亲 组件通信?
document.querySelector('zf-collapse').dispatchEvent(new CustomEvent('changeName', {
detail: {
name: this.getAttribute('name'),
isShow: this.isShow
}
}))
})
}
static get observedAttributes() { // 监控属性的变化
return ['active', 'title', 'name']
}
// update
attributeChangedCallback(key, oldVal, newVal) {
switch (key) {
case 'active':
this.activeList = JSON.parse(newVal); // 子组件接受父组件的数据
break;
case 'title':
this.titleEle.innerHTML = newVal; // 接受到title属性 作为dom的title
break;
case 'name':
this.name = newVal
break;
}
let name = this.name;
if (this.activeList && name) {
this.isShow = this.activeList.includes(name);
this.shadowRoot.querySelector('.content').style.display = this.isShow ? 'block' : 'none'
}
}
}
export default CollapseItem
- 页面使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<zf-collapse>
<zf-collapse-item title="Node" name="1">
<div>nodejs welcome</div>
</zf-collapse-item>
<zf-collapse-item title="react" name="2">
<div>react welcome</div>
</zf-collapse-item>
<zf-collapse-item title="vue" name="3">
<div>vue welcome</div>
</zf-collapse-item>
</zf-collapse>
<!-- 没有实际意义, 不会渲染到页面上 -->
<template id="collapse_tmpl">
<div class="zf-collapse">
<slot></slot>
</div>
</template>
<template id="collapse_item_tmpl">
<div class="zf-collapse-item">
<div class="title"></div>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<!-- vite 实现原理 就依赖于 type="module" -->
<script src="./index1.js" type="module"></script>
</body>
</html>
更多:
案例Demo(https://github.com/mdn/web-components-examples)
猜你喜欢
- 2024-11-15 Velocity.js 动画库的使用(vue常用的动画库)
- 2024-11-15 【CSS】响应式网页设计(响应式网页怎么设计)
- 2024-11-15 移动前端重构实战系列:5-7章(前端移动端主要技术有哪些)
- 2024-11-15 2019年我总结的前端面试题(2021年前端面试题汇总)
- 2024-11-15 使用RKE的方式快速部署K8S集群(k8s部署zookeeper集群)
- 2024-11-15 手把手教你实现一个Vue自定义指令懒加载
- 2024-11-15 怎么学习前端开发?(怎么样才能学好前端开发)
- 2024-11-15 2019 大龄前端如何准备面试?(大龄前端怎么找到工作)
- 2024-11-15 入门难吗?该如何学习前端(初学前端)
- 2024-11-15 Web前端入门学习路径:教你4步走(web前端快速入门)
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-