最近在思考如果脱离框架如何实现自定义组件这个问题,经过一番研究之后发现web API提供了web component来给用户实现自定义组件,就拿Antd的Button组件为例,废话少说直接上代码:
index.html
<!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>
<script type="module" src="./index.js"></script>
<button-wrapper size="large">按钮</button-wrapper>
<div style="margin-top: 30px;">
<button>small</button>
<button>middle</button>
<button>large</button>
</div>
<script type="text/javascript">
document.querySelectorAll('button').forEach(item=>{
item.addEventListener('click',()=>{
document.querySelector('button-wrapper').setAttribute('size',item.innerText);
})
})
</script>
</body>
</html>
index.js
import Button from "./button.js";
//通过customElements.define实现自定义元素,但是为了和html元素区分,自定义元素的命名只只支持小写字母并且带“—”中横线
customElements.define('button-wrapper', Button);
button.js
在web component中也提供相应的生命周期函数可以监听到元素被挂载或者移除等动作,并且通过static get observedAttributes方法返回需要监听的自定义属性数组当属性值发生改变时就会触发attributeChangedCallback这个生命周期函数
export default class Button extends HTMLElement {
constructor() {
super()
// 创建 shadow root
var shadow = this.attachShadow({ mode: 'open' });
// 创建 button
var wrapper = document.createElement('button');
wrapper.setAttribute('class', 'large');
wrapper.innerHTML = this.innerHTML;
this.textContent = "";
var style = document.createElement('style');
// 将所创建的元素添加到 Shadow DOM 上
shadow.appendChild(style);
shadow.appendChild(wrapper);
}
updateStyle(elem) {
var shadow = elem.shadowRoot;
shadow.querySelector("style").textContent = `
.large{
font-size: 16px;
height: 40px;
padding: 6.428571428571429px 15px;
border-radius: 8px;
color: #fff;
background-color: #1677ff;
box-shadow: 0 2px 0 rgba(5,145,255,.1);
outline: none;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
cursor: pointer;
transition: all .2s cubic-bezier(.645,.045,.355,1);
user-select: none;
touch-action: manipulation;
line-height: 1.5714285714285714;
position: relative;
${elem.getAttribute('style')}
}
.middle{
font-size: 14px;
height: 32px;
padding: 4px 15px;
border-radius: 8px;
color: #fff;
background-color: #1677ff;
box-shadow: 0 2px 0 rgba(5,145,255,.1);
outline: none;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
cursor: pointer;
transition: all .2s cubic-bezier(.645,.045,.355,1);
user-select: none;
touch-action: manipulation;
line-height: 1.5714285714285714;
position: relative;
${elem.getAttribute('style')}
}
.small{
font-size: 14px;
height: 24px;
padding: 0px 7px;
border-radius: 8px;
color: #fff;
background-color: #1677ff;
box-shadow: 0 2px 0 rgba(5,145,255,.1);
outline: none;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
cursor: pointer;
transition: all .2s cubic-bezier(.645,.045,.355,1);
user-select: none;
touch-action: manipulation;
line-height: 1.5714285714285714;
position: relative;
${elem.getAttribute('style')}
}
`;
}
updateClass(elem,newValue){
var shadow = elem.shadowRoot;
shadow.querySelector('button').setAttribute('class',newValue)
}
static get observedAttributes() { return ['size', 'style']; }
connectedCallback() {
console.log('自定义组件被添加到页面中.');
this.updateStyle(this);
}
disconnectedCallback() {
console.log('自定义元素被从页面中移除');
}
adoptedCallback() {
console.log('自定义元素被移动其他页面中');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log('自定义组件属性发生改变');
if(name=='size'){
this.updateClass(this,newValue)
}else{
this.updateStyle(this);
}
}
}
原生自定义组件的好处是自身就像是一个暗箱,隔离了自身和外界的style不会存在样式冲突的问题,只能通过自己实现改变元素的样式。