Web Components & Vue
记录学习过程,如有错误欢迎指出
Custom Elements
Custom Elements给予了开发者自定义标签的能力,能够通过继承
HTMLElement获得标签钩子。也可以扩展标签,继承自HTMLButtonElement等内建元素
一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们----摘自MDN
Custom Elements分为两种
- Autonomous custom elements(自主自定义标签) 继承自
HTMLElement - Customized built-in elements (自定义内建元素) 扩展内置HTML元素,如
HTMLButtonElement,HTMLDivElement...
Autonomous custom elements
//这里是自定义一个标签并且继承自HTMLElement
class FirstElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
//元素被添加进DOM后,会触发这个方法
}
disconnectedCallback() {
//元素在DOM中被移除时,将会触发这个方法
}
.....
}
//注册自定义标签FirstElement,让浏览器自动这个标签是一个自定义标签
//并且这里的标签命名必须时由-(中划线)分割的,原因是避免和内建 HTML 元素之间发生命名冲突
customElements.define("first-element", FirstElement);
例子:
//html
<html>
<head>
...
</head>
<body>
<first-element data="hello">test</first-element>
</body>
</html>
//js
class FirstElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
console.log(this)
//这里的this指向这个自定义标签,所以是可以通过this拿到这个标签的所有属性的
//比如:offsetHeight,attribute等
console.log(this.getAttribute('data')) //hello
}
}
customElements.define("first-element", FirstElement);
注意:在
connectedCallback中渲染,而不是constructor中。
原因1.是因为
constructor被调用的时候元素虽然已经被创建,但是还未没被插入到DOM中,这样就会导致通过this取得属性时的结果为null
原因2.因为
connectedCallback方法只会在元素被插入到DOM中被触发,那么如果我们不插入就不触发从而就不会渲染元素,如果在constructor中,那么一旦FirstElement被实例化就代表了渲染,有时我们可能不需要渲染,所以在connectedCallback中渲染也考虑到了性能原因
监听变化
//html
<html>
<head> ... </head>
<body>
<second-element data="1"></second-element>
</body>
</html>
//js
class SecondElement extends HTMLElement{
constructor(){
super()
}
render(data=1){
this.innerHTML = data;
}
connectedCallback(){
//防止出现首次渲染后,attributeChangedCallback监听到变化,又调用render,render又触发connectedCallback,也就是防止死循环,只有当isRender不存在时才触发render()
if(!this.isRender){
render()
this.isRender = true
}
}
//
static get observedAttributes() {
///属性数组,这些属性的变化会被监视attributeChangedCallback方法监听
return ['data']
}
//当上面数组中的属性发生变化的时候,这个方法会被调用
attributeChangedCallback(name, oldValue, newValue) {
this.render(newValue)
}
}
customElements.define("second-element", SecondElement);
//2秒后更改second-element标签的data属性,触发attributeChangedCallback()
setTimeout(()=>{element.setAttribute('data',2)},2000)
获取子节点问题
即如果一个自定义标签在渲染后
connectedCallback中获取改自定义标签的子节点,但是子节点还没有被渲染出来,导致获取不到,这时我们就应该将connectedCallback内部获取子节点的操作改为0ms的定时器,即改为宏任务,在下一次的事件循环中获取子节点,那是子节点已经渲染完毕,所以能够正常获取信息
Customized built-in elements
我们所创建的自定义标签,并没有赋予任何语义,搜索引擎不认识,无障碍阅读也不能正常识别,所以我们可以复用内置的HTML元素,使得标签符合我们的业务逻辑,并且具有语义等信息
例子
//html
//现在这个button具有我们自己实现的逻辑,也具有内置button的所有属性
<button say="hello-button">click</button>
//js
class HelloButton extends HTMLButtonElement {
constructor() {
super()
//当具有hello-button属性的button被创建时,向其自身添加一个事件处理函数
this.addEventListener('click', () => console.log("hello"))
}
}
//注意第三个属性
customElements.define('hello-button', HelloButton, {extends: 'button'})
Vue-defineCustomElement
Vue通过封装了
HTMLElement,暴露出defineCustomElement给开发者使用,最终还是通过customElements.define注册,并且内部的钩子和就是Custom Elements中的钩子,而且Vue官方也要求用户编写的组件名是以-(中划线)分割的
shadow DOM
DOM 元素可以有以下两类 DOM 子树:
- Light tree - 常规DOM子树,由HTML元素组成,我们常见的元素基本上都是Ligth的
- Shadow tree - 隐藏的DOM子树,不在HTML中反映出来
内建Shadow DOM
<input type="text">
<!-- 打开chrome控制台开启‘Show user agent shadow DOM’选项,即可查看到input的内置shadow dom -->
shadow dom:
<input type="text">
#shadow-root (user=agent)
<div></div>
</input>
如果一个元素同时有以上两种子树,那么浏览器只渲染 shadow tree。但是我们同样可以设置两种树的组合
影子树可以在自定义元素中被使用,其作用是隐藏组件内部结构和添加只在组件内有效的样式
//在自定义元素中使用shadow dom
class ShadowElement extends HTMLElement {
connectedCallback(){
const shadow = this.attachShadow({mode:'open'})
shadow.innerHTML = `<div>
${this.getAttribute('name')}
</div>`
}
}
customElements.define('shadow-element',ShadowElement)
//html
<shadow-element name="jack"></shadow-element>
//最终被渲染出来的就是jack
//渲染出来的shadow dom结构
<shadow-element name="jack">
$shadow-root(open)
<div>jack</div>
</shadow-element>
这里需要注意的是
this.attachShadow({mode:'open'}mode有两个可选值:
- {mode:"open"}:shadow root 可以通过
elem.shadowRoot访问任何代码都可以访问elem的 shadow tree - {mode:"closed"}:element.shadowRoot 永远是
null
Vue slot
思考一下slot的形式是不是和shadow dom很接近,shadow dom也有插槽这个概念,如具有插槽
两者都是在元素上定义slot属性,然后在
connectedCallback进行判断渲染
模板元素
内建的
<template>元素用来存储 HTML 模板。浏览器将忽略它的内容,仅检查语法的有效性,但是我们可以在 JavaScript 中访问和使用它来创建其他元素
<template>
<div>1</div>
</template>
css和js一样可以存在于模版中
<template>
<style>
...
</style>
<script>
...
</script>
</template>
总结
<template>的内容可以是任何语法正确的 HTML。<template>内容被视为“超出文档范围”,因此它不会产生任何影响。- 我们可以在JavaScript 中访问
template.content,将其克隆以在新组件中复用。
Vue template语法
个人认为Vue的template语法应该是受模版元素的启发,从而设计出来的,区别在于用户编写的template是交由Vue解析器解析后生成render函数
最后
可以看出Vue的template就是模板元素+shadow dom + 自定义元素,使得我们可以在Vue中使用template语法,虽然编写的template最后都变成了render函数,但是在对于新手还是比直接编写render函数友好的
记录学习过程,如有错误欢迎指出