主要知识点
- 组件封装
- 合并配置
- 事件委托
- 预定义EventTarget类
- 通过继承扩展组件功能
- webComponent自定义组件
组件封装
数据和方法的简单封装。例如一个对话框,一个按钮,一个导航条。里面包含样式,骨架,方法。健康得组件还能通过简单的配置,满足不同情况的需求。现在热门element ui 等框架里面就有很多复用性的很强的组件。如果我们想要封装组件该怎么办呢。今天我就来简单实践。
这次实验是封装一个简单的提示框:
模块化开发
-
合并配置
- 创建一个Dialog类,提供一个默认的配置。由于我们用户可以自定义配置所以我们需要将默认配置和用户自定义配置融合。当然可以用Object.assign 也可以用es6 的展开运算符
...
- 创建一个Dialog类,提供一个默认的配置。由于我们用户可以自定义配置所以我们需要将默认配置和用户自定义配置融合。当然可以用Object.assign 也可以用es6 的展开运算符
export default class Dialog {
constructor(userOpts) {
this.opts = {
width: '400px',``
height: "250px",
title: "测试标题",
content: "测试内容",
draggable: true, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: true //是否有取消
}
this.opts = Object.assign(this.opts, userOpts);
-
触发对话框
为了测试方便创建一个button 用于触发对话框,并且添加点击事件
import dialog from './dialog.js'; //引入模块 const btn = document.getElementById('btn'); const myDialog = new dialog( //创建myDialog 实例并且传入对象 { width:"500px", title: "我的标题", maskable: true, isCancel: false } ); btn.addEventListener('click',function () { myDialog.open(); //触发对话框 }) -
初始化组件
-
类里面创建一个创建组件类。用于创建组件。通过模板字符。对组件类的相关配置使用
${}来实现用户的自定义配置。 -
值得注意的是
let container的作用域在 creatElement() 函数中。所以要通过this.container = container;指向实例的container 这样才能在别的函数中使用这个变量
creatElement(){ let container = document.createElement('div'); container.innerHTML = ` ${this.opts.maskable? '<div class="k-wrapper"></div>':''} <div class="k-dialog" style="width:${this.opts.width};height:${this.opts.height}"> <div class="k-header"> <span class="k-title">${this.opts.title}</span><span class="k-close">X</span> </div> <div class="k-body"> <span>${this.opts.content}</span> <input class="input-inner" type="text" /> </div> <div class="k-footer"> ${this.opts.isCancel? '<span class="k-default">取消</span>':''} <span class="k-primary">确定</span> </div> </div> ` container.style.display = 'none'; document.body.appendChild(container); this.container = container; } -
-
建立初始化函数
初始化函数的目的在于,简化
constructor构造函数的冗余。例如这种初始化组件,添加组件事件之类的方法。需要在new 实例的时候就要的执行。如果都写在constructor中就会冗余。那么我们可以在创建init()函数。里面放入这些需要初始化的方法。在constructor中this.init()执行一下,就让constructor清爽很多了//初始化配置/查看配置 init() { console.log(this.opts); //初始化组件 this.creatElement(); //初始化事件类 this.addEvent(); } -
功能函数
-
open 打开提示框
//显示提示框得方法 open() { this.container.style.display = 'block'; } -
关闭提示框
close(){ this.container.style.display = 'none'; } -
拖拽功能
drag(){ let kDialog = this.container.querySelector('.k-dialog'); kDialog.onmousedown = function (e) { let startX = e.clientX - this.offsetLeft; let startY = e.clientY - this.offsetTop; this.onmousemove = function (e) { let nowX = e.clientX; let nowY = e.clientY; this.style.left = nowX - startX + 'px'; this.style.top = nowY - startY + 'px'; } } document.onmouseup = function () { kDialog.onmousemove = '' } }
-
-
事件委托
老生常谈的面试题
一个提示框中,按确定按钮,取消按钮,关闭按钮都可以让组件框关闭,但是这个一个个按钮添加事件比较麻烦。所以我们利用事件冒泡。在父级添加事件。利用event.target 做一个事件委托
addEvent(){ //事件委托的方法 let closeEvent = this.container.querySelector('.k-dialog'); closeEvent.addEventListener('click',(e)=>{ //使用箭头函数避免this指向closeEvent,而不是这个类的中的实例。因为closeEvent 实例中没有 close() 方法。 //获取事件的类 console.log(e.target.className); switch (e.target.className) { case 'k-close' : this.close(); break; case 'k-default' : this.close(); break; case 'k-primary' : this.close(); break; default : break; } }) } -
自定义事件
我们提示框中有确定按钮和取消按钮。有可能会现象有回调函数的需求。
我们有三种方式可以添加这种事件
-
事件委托:例如上面的直接在case里面添加 this.opts.success()
- 这种方式比较直接,代码量也比较少
- 难于扩展
-
自定义事件类
- 参考笔记 可以自己封装一个事件管理类,然后继承这个类
- 在init()中 addEvent("success",this.opts.success); 添加事件绑定
- 在需要的地方触发一下就可以了 this.triggerEvent("myEvent");
-
EventTarget 类 利用系统自带的类
- 具体看下面代码
- mdn
例如这样:
-
const myDialog = new dialog(
{
width:"500px",
title: "我的标题",
success(){
console.log('success..');
},
cancel(){
console.log('cancel..');
}
}
);
//Dialog 类的默认配置添加初始化
constructor(userOpts) {
this.opts = {
width: '400px',
height: "250px",
title: "测试标题",
content: "测试内容",
draggable: true, //是否可拖拽
maskable: true, //是否有遮罩
isCancel: true, //是否有取消
success() {
},
cancel() {
}
}
//继承 EventTarget
export default class Dialog extends EventTarget{
...
//添加事件绑定
//初始化配置/查看配置
init() {
this.creatElement();
this.addEvent();
// *** 添加事件绑定 ***
this.addEventListener('success',this.opts.success);
this.addEventListener('cancel',this.opts.cancel);
}
...
//事件触发
//实例化 CustomEvent('success'); .
//触发函数为dispatchEvent(sEvent);
...
//获取事件的类
switch (e.target.className) {
case 'k-close' :
this.close();
break;
case 'k-default' :
let cEvent = new CustomEvent('cancel');
this.dispatchEvent(cEvent);
this.close();
break;
case 'k-primary' :
let sEvent = new CustomEvent('success');
this.dispatchEvent(sEvent);
this.close();
break;
default :
break;
}
}
继承扩展组件功能
如果后续需求中要求在原始的组件添加功能。那么可以通过继承类的形式去拓展。例如我们需要一个带input框的的提示框
-
创建一个inputDialog类继承自Dialogl类
添加input 元素。然后再外部创建inputDialog实例。由于是继承的,所以父类的方法都是可以用的
export class inputDialog extends Dialog { constructor(options) { super(options); this.creatInputElement(); } creatInputElement() { let inpEle = document.createElement('input'); let dBody = this.container.querySelector('.k-body'); inpEle.classList.add('input-inner'); dBody.appendChild(inpEle); } } -
传递参数
有Input元素。我们希望可以点击确定时候可以传递参数到回调函数里面。那么可以利用EventTarget 自有属性做到这一点
参考mdn 里面CustomEvent 提供了一个details属性可以传递参数。
class MyEventTarget extends EventTarget { constructor(mySecret) { super(); this._secret = mySecret; } get secret() { return this._secret; } }; let myEventTarget = new MyEventTarget(5); let value = myEventTarget.secret; // == 5 myEventTarget.addEventListener("foo", function(e) { this._secret = e.detail; }); let event = new CustomEvent("foo", { detail: 7 }); myEventTarget.dispatchEvent(event); let newValue = myEventTarget.secret; // == 7首先我们需要将CustomEvent功能在自定义事件内抽离出来,再词重写函数传入参数
confirm(value){ let sEvent = new CustomEvent('success',{detail:value}); this.dispatchEvent(sEvent); } ... //确定按钮中调取函数 case 'k-primary' : this.confirm(); this.close(); break; ... //inputDialog 类 confirm() { super.confirm(this.inpEle.value); this.inpEle.value = ''; }再外部配置的回调函数中 通过e.detail可以难道参数
const myDialogI = new inputDialog( { width:"400px", height:'250px', title: "我的标题", success(e){ console.log('success..',e.detail); }, cancel(){ console.log('cancel..'); } } );
webComponent自定义组件
教程
MDN 教程
自定义标签教程 它使开发者能够将HTML页面的功能封装为 custom elements(自定义标签)
它和现在流行的组件库还是挺像,它具有的一些特性
- 通过标签调用组件
- 通过属性传递参数
使用方式
- 自定义组件, 自定义独立元素
- 在body 标签中 使用自己的定义标签
// 自定义组件, 自定义独立元素;
class MyComponet extends HTMLElement{
constructor(){
super();
// console.log("this",this);
this.innerHTML = "<button>按钮</button>";
}
}
customElements.define("my-component",MyComponet);
//html
<my-component></my-component>
//这时候页面就会显示按钮
-
继承HTLML元素的类对它进行扩展
- 继承html元素
- customElements.define 定义标签
- 利用
is关键字说明这个img 元素有扩展
// 继承html元素; // 让图片延迟显示 class MyImg extends HTMLImageElement{ constructor(){ super(); setTimeout(()=>{ this.src = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg" },1000) } } customElements.define("my-img",MyImg,{ extends:"img" }) //html <img src="#" is="my-img" />
通过webComponent 对Dialog 组件封装
-
继承HTMLElement,新创建Dialog实例对象
-
定义
customElements.define('web-dialog',webDialog); -
在自定义标签中添加按钮触发Dialog.open()
-
设置get title() 方法 传递自定义属性this.getAttribute('title') ?? '默认自定义组件标题'
-
值得注意的是如果想传入布尔值
JSON.parse(this.getAttribute('draggable')) ;需要json系列化一下 -
触发函数方面可以使用EVENTTARGET类 绑定事件在html 中触发
class webDialog extends HTMLElement{ constructor() { super(); this.innerHTML = `<button>WebClick</button>` let dialog = new Dialog({ title: this.title, draggable: this.draggable, //触发事件 success: ()=>{ this.dispatchEvent(new CustomEvent('success')); } }); this.onclick = function () { console.log(dialog.opts); dialog.open(); } } get title(){ return this.getAttribute('title') ?? '默认自定义组件标题' } get draggable(){ return JSON.parse(this.getAttribute('draggable')) ; } } customElements.define('web-dialog',webDialog); //HTML document.querySelector('web-dialog').addEventListener('success',function () { console.log('自定义标签的确定回调事件'); })