我们在使用Web Components自定义组件的时候,我们需要继承HTMLElement这个浏览器内置对象,但是如果我要一些高级封装,给组件内置一些方法的话。我们就需要使用继承的方式,在父类中实现基本功能的封装。
1 父类的封装
以下是我的继承封装:
import Emitter from "./index.js";
import { loadHtmlFile } from "./index.js";
export default class PageElement extends HTMLElement {
constructor() {
super();
this.emits = Emitter.emits;
this.loadHtml = this.loadHtml;
this.appendHTML = this.appendHTML;
}
loadHtml(path) {
}
appendHTML(html) {
}
bindEvent() {
}
beforeMounted() {
}
mounted() {
}
beforeDestroy() {
}
destroyed() {
}
}
父类中完成了一些基本方法的配置。我这个父类中利用HTMLElement自身的一些特性,实现对组件生命周期的重新封装。以及一些基础方法。以及绑定事件的处理。
1 loadHTML方法
这个方法是我具体是去实现加载html的方法,即我的组件是由三部分组层的html,css,js三部分完成。css上期我们已经讲过了使用link标签完成加载。
html我们使用fetch方法去加载,然后将fetch封装成为一个异步方法,这样我们就可以去加载html.
最后由这个js去完成组件功能的封装。
加载html我的封装方法如下:
export function loadHtmlFile(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => {
if (response.ok) {
return response.text();
}
})
.then((htmlContent) => {
resolve(htmlContent);
})
.catch((error) => {
reject(error);
});
});
}
constructor() {
super();
this.emits = Emitter.emits;
this.loadHtml = this.loadHtml;
this.appendHTML = this.appendHTML;
}
loadHtml(path) {
return new Promise((resolve, inject) => {
loadHtmlFile(path).then((html) => {
resolve(html);
this.beforeMounted();
}).catch((error) => {
console.warn("html加载失败", error.toString());
})
})
}
这样我们就可以在加载完成html之后,去触发我们自己定义的beforeMounted方法。
2 appendHTML方法
appendChild方法,触发我们真实的加载完成方法。mounted。这里必须要讲一下,组件自身带的connectedCallback这个勾子函数是不符合我的使用场景的,因为虽然标签已经注册到页面上,并且也调用了对应的标签,但是这个时候我的html并没有加载完成。所以我自己的mounted生命周期才是真正的组件内部html代码全部加载完成。
下面我们看一个具体定义组件的例子:
export class pageError extends PageElement {
constructor() {
super();
if (!this.shadowRoot) {
this.attachShadow({ mode: "open" });
}
const style = document.createElement("link");
style.setAttribute("rel", "stylesheet");
style.setAttribute("href", "./src/page/error/index.css");
this.shadowRoot.appendChild(style);
this.loadHtml("./src/page/error/index.html").then((html) => {
const template = document.createElement("div");
template.innerHTML = html;
this.setAttribute("class", "page-error");
this.appendHTML(template);
});
}
mounted() {
console.log("loaded");
}
backHome() {
this.app.router.push("/");
}
}
这是我做的一个404页面组件。
继承了PageElement,并且使用了父类中的loadHTML方法和appendHTML方法。
这个地方之所以用父类的方法,是因为父类需要在这两个方法里面去做一些特殊处理。上面已经演示了具体做了什么。
2 页面组件的组成:
下面演示一下我的组件三部分组成:
通过以上方法的封装,我们讲一个组件拆分成为三部分。
3 路由:
那么我们怎么根据路由变化,来加载不同的页面了。我这里封装了路由,通过拦截路由我们来完成页面加载。具体的做法我们后面再讲。
这里需要用html4 history对象的history.pushState方法以及,popstate的监听事件。当然我们这里默认指的时history模式,而不是hash模式。而且这个popstate事件,并不是用来做跳转监听的,想要实现对路由的拦截,是需要重新写着一部分逻辑的。后面我们继续分享这一块的知识。
这里还有一个特殊处理,就是事件绑定处理。
4 事件绑定处理
我们实现了类似vue的事件绑定方式:
<div class="desc">
<div class="desc-text">非常抱歉,当前页面无法访问,可能原因:</div>
<div class="desc-content">
1、您输入了错误的网页地址 <br />
2、原网页已经删除或下线
</div>
<div class="desc-btn" @click="backHome">返回网站首页</div>
</div>
注意看这里@click="backHome"
<div class="menu" @click="() => this.skip(2)">
<div class="icon">
<custom-icon src="./src/assets/svg/api.svg" />
</div>
<div class="text">新html5 API</div>
</div>
注意看这里@click="() => this.skip(2)"
这种时间绑定的处理方法,我们都是在页面加载环节,在appHTML方法里面完成的属性解析以及对当前属性的节点进行事件监听方法的绑定处理。
同样,我们再看404页面中我们绑定方法backHome方法,我们实践代码如下:
backHome() {
this.app.router.push("/");
}
this.app.router.push("/"),这里我们就完成了跳转页面,而这里则就是我们给页面组件中注入了路由对象,然后通过路由的方法完成跳转。
这里的路由对象的封装,我们后续继续分享。
当然,我的个人基于webComponents框架,已经即将封装完成。后续和大家见面和分享这里面的技术知识点。