Web Components 原生组件的封装(template、slot、生命周期、传参)
先上图看效果user-card就是一个原生的组件,1,支持传入图像、姓名等props2,支持使用slot,插入html片段,使用同vue类似3,支持使用template标签,写组件模板4,支持调用组件的方法,也可以传入方法给组件调用5,兼容性良好,纯原生,主流浏览器都兼容核心API1,自定义元素customElements.define(tag-name,tagClass,{extends:tag
先上图看效果,再说原理
user-card就是一个原生的组件的实列,
1,支持传入图像、姓名等props,prop更新时自动更新视图
2,支持使用slot,插入html片段,使用同vue类似
3,支持使用template标签,写组件模板
4,支持调用组件的方法,也可以传入方法给组件调用
5,兼容性良好,纯原生,主流浏览器都兼容
概念
组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。
谷歌公司由于掌握了 Chrome 浏览器,一直在推动浏览器的原生组件,即 Web Components API。
相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。
使用场景
个人认为他当前可以承担的的角色:公共组件。
在不同框架(vue,react,angular等等),写的项目中,用同一组件实现同一功能。
核心API
1,自定义元素
customElements.define(tag-name,tagClass,{extends:tagname})
第一个参数:标签名,用 - 横线连接小写子母
第二个参数:标签的类
eg:
class MyButton extends HTMLElement {
constructor() {
super();
this.style.cssText = `display:block;background:red;width:200px;height:30px;line-height:30px;text-align:center;`;
this.addEventListener("click", () => alert("我是自定义的按钮"));
this.append("自定义按钮");
}
}
window.customElements.define("my-button", MyButton);```
使用:
```xml
<my-button>666 </my-button>
第三个参数:是继承的标签名(会获取标签的基础样式等),使用时用is 指定是哪个自定义元素。
window.customElements.define("my-button2", MyButton2, {
extends: "button",
});
使用:
<button is="my-button2">extends Button</button>
2,template
template是一个原生的标签,他的内容不会渲染在页面上
有了template, 就不需要用js来创建dom,结构了
<template id="userCardTemplate">
<img src="./img/1.webp" class="image" />
<div class="container">
<p class="name">李四</p>
<p class="email">lisil@some-email.com</p>
<button class="button">Follow</button>
</div>
<style>
user-card {
display: flex;
align-items: center;
width: 450px;
height: 180px;
background-color: #d4d4d4;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
}
.image {
width: 200px;
margin-right: 20px;
}
</style>
</template>
js:
class UserCard extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById("userCardTemplate");
console.log(templateElem.constructor.name); //HTMLTemplateElement
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
window.customElements.define("user-card", UserCard);
3,组件的传参和生命周期钩子
1, 传参同原生
2,生命周期
connectedCallback:当 custom element首次被插入文档DOM时,被调用。
disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
adoptedCallback:当 custom element被移动到新的文档时,被调用。
attributeChangedCallback: 当 customelement增加、删除、修改自身属性时,被调用。
observedAttributes:监视属性的更新,类似useEffect,的第二个参数
class UserCard3 extends HTMLElement {
//
// useEffect(() => {
// if (appDataUrl) {
// findCacheVideo();
// }
// }, [appDataUrl]);
static get observedAttributes() {
return ["img", "name", "email", "buttonclick"];
}
constructor() {
super();
var templateElem = document.getElementById("userCardTemplate");
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
// 插入dom时
connectedCallback() {
this.querySelector(".input").focus();
}
// 属性更新、删除、增加; 首次插入dom也会触发
attributeChangedCallback(name, oldValue, newValue) {
console.log(name, oldValue, newValue);
this.updateAttr(name, newValue);
}
updateAttr(name, value) {
// 此处投机取巧
var el = this.querySelector(`.${name}`);
if (name === "img") {
el.setAttribute("src", value);
} else if (name === "buttonclick") {
el.onclick = new Function(value);
} else {
el.innerText = value;
}
}
// 提供api把内部的值传递出去
getInputValue() {
return this.querySelector(".input").value;
}
}
window.customElements.define("user-card3", UserCard3);
4,shadow DOM
Web components的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,ShadowDOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM附加到一个元素上。
主要作用隔离,防止受到外部样式,js的影响
改写:
class UserCard3 extends HTMLElement {
static get observedAttributes() {
return ["img", "name", "email", "buttonclick"];
}
constructor() {
super();
var templateElem = document.getElementById("userCardTemplate");
var content = templateElem.content.cloneNode(true);
// 创建影子dom
this.shadowDOM = this.attachShadow({ mode: "closed" });
this.shadowDOM.appendChild(content);
}
// 插入dom时
connectedCallback() {
this.shadowDOM.querySelector(".input").focus();
}
// 属性更新、删除、增加; 首次插入dom也会触发
attributeChangedCallback(name, oldValue, newValue) {
// console.log(name, oldValue, newValue,this);
this.updateAttr(name, newValue);
}
updateAttr(name, value) {
var el = this.shadowDOM.querySelector(`.${name}`);
if (name === "img") {
el.setAttribute("src", value);
} else if (name === "buttonclick") {
el.onclick = new Function(value);
} else {
el.innerText = value;
}
}
// 提供api把内部的值传递出去
getInputValue() {
return this.shadowDOM.querySelector(".input").value;
}
}
window.customElements.define("user-card3", UserCard3);
5,slot
slot是一个原生的标签,本身不会被渲染在页面上,但是内容会
vue的slot模仿了原生slot的基本使用方式, 所以slot的使用就是你想象的那样
<template id="userCardTemplate">
<img src="./img/2.webp" class="img" />
<div class="container">
<p class="name">姓名</p>
<p class="email">邮箱</p>
<button class="buttonclick">Follow</button>
<input type="text" class="input" value="1" />
</div>
<slot></slot>
<slot name="likes">我的爱好是:敲代码</slot>
<slot name="eat">我喜欢吃:肉肉</slot>
<style>
* {
box-sizing: border-box;
}
:host {
display: flex;
align-items: center;
width: 650px;
height: 280px;
background: #d4d4d4 !important;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
/* border: 1px solid red; */
}
.img {
width: 200px;
margin-right: 20px;
}
</style>
</template>
封装和源码
1,user-card封装
userCard.js
避免页面写模板,写好后,复制采用字符串的形式
window.addEventListener("load", () => {
class UserCard extends HTMLElement {
static get observedAttributes() {
return ["img", "name", "email", "buttonclick"];
}
constructor() {
super();
const template = document.createElement("template");
template.innerHTML = `
<img src="./img/2.webp" class="img" />
<div class="container">
<p class="name">姓名</p>
<p class="email">邮箱</p>
<button class="buttonclick">Follow</button>
<input type="text" class="input" value="1" />
</div>
<slot name="likes">我的爱好是:敲代码</slot>
<slot name="eat">我喜欢吃:肉肉</slot>
<style>
:host {
display: flex;
align-items: center;
width: 650px;
height: 280px;
background: #d4d4d4!important;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
}
.img {
width: 200px;
margin-right: 20px;
}
</style>
`;
// 创建影子dom
this.shadowDOM = this.attachShadow({ mode: "closed" });
this.shadowDOM.appendChild(template.content.cloneNode(true));
}
// 插入dom时
connectedCallback() {
this.shadowDOM.querySelector(".input").focus();
}
// 属性更新、删除、增加; 首次插入dom也会触发
attributeChangedCallback(name, oldValue, newValue) {
// console.log(name, oldValue, newValue);
this.updateAttr(name, newValue);
}
updateAttr(name, value) {
var el = this.shadowDOM.querySelector(`.${name}`);
if (name === "img") {
el.setAttribute("src", value);
} else if (name === "buttonclick") {
el.onclick = new Function(value);
} else {
el.innerText = value;
}
}
// 提供api把内部的值传递出去
getInputValue() {
return this.shadowDOM.querySelector(".input").value;
}
}
window.customElements.define("user-card", UserCard);
});
2,应用源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>简单的封装一下</title>
<script src="./src/userCard.js"></script>
</head>
<body>
<user-card
id="user-card3"
img="./img/2.webp"
name="王五3"
email="110@qq.com"
buttonclick="test()"
>
<div slot="likes" class="likes">
<h3>我的爱好有很多:</h3>
<p>🏀</p>
<p>🎣</p>
<p>🏸</p>
</div>
<div slot="eat" class="eat">
<h3>我爱吃的很多:</h3>
<p>🐟</p>
<p>🍚</p>
<p>🦆</p>
</div>
</user-card>
<br />
<button onclick="getUserCardInputValue()">
读取user-card3的输入框的value
</button>
<br />
<script>
window.addEventListener("load", () => {
var card3 = document.getElementById("user-card3");
setTimeout(() => {
card3.setAttribute("img", "./img/3.webp");
card3.setAttribute("name", "赵四");
// test没有observed 所有不会触发attributeChangedCallback钩子
card3.setAttribute("test", "test");
}, 2000);
});
// 读取组件内部的值
function getUserCardInputValue() {
var card3 = document.getElementById("user-card3");
alert(card3.getInputValue());
}
// 给组件传递一个方法
function test() {
alert("test函数");
}
</script>
</body>
</html>
组件库
更多推荐
所有评论(0)