Web Component 是Html5 app开发定制推出的一个新特征,即web 组件。提到webapp开发定制组件有一定经验的人对app开发定制这个词汇并不陌生,app开发定制在这个严重依赖前端框app开发定制架来开发项目的时代,app开发定制我们推崇使用组件化的app开发定制思路来编写我们的页面,app开发定制通过这些低耦合的组件我们可以像搭积木一样组织出我们的页面。
从我们的日常所见到的类似table select 这些耳熟能详的基础标签,更有后来更复杂的video raido等,都是web的一种体现。可惜的是兼容性问题以及api的丰富度不够。目前主流还是使用框架来模拟组件的实现。随着浏览器兼容的越来越好,如果能完全使用web component来组织我们的项目,我们可以少引入很多第三方的框架来编排我们的页面
本文我们将来探究 Web Component
自定义 Web Component
关键的api,它是挂载在window上的api,并非document。
window.customElements.define()// 参数类型// name 是组件名称// constructor 自定义组件的构造类// options 更多的属性(method) CustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 自定义内置元素
自定义内置元素的本质是扩展已有的内置元素的逻辑。他的好处是可以直接继承默认内置元素的例如语义、默认交互。用户只需要关注修改自定义交互即可。
我们来自定义一个button 实现一个点击默认的操作。
class MyButton extends HTMLButtonElement { constructor () { // 必须加super 否则this 无法指向button super() this.addEventListener('click', function () { alert('this is my button') }) } } customElements.define('my-button', MyButton, { extends: "button" })
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
申明的时候,需要通过extends传入一个已经存在的内置元素,调用的时候通过内置元素的is来扩展,
<button is="my-button">Click me</button>
- 1
- 定义一个完全自治的元素
完全自治的元素,不依赖现有元素。是一个完全从view 到数据 到交互都自我管理的自定义元素。如上述他的缺点就在于开发者需要处理所有的语义和交互,写法比较繁琐。如果处理不好,会导致违背html语义标签的一些标准。
class TextIcon extends HTMLElement { constructor() { super() this._text = null; } static observedAttributes = ['text']; attributeChangedCallback(name, oldValue, newValue) { this._text = newValue; this.updateRender(); } connectedCallback() { this.updateRender(); } get text () { return this._text; } set text(v) { console.log(v, 'ss') this.setAttribute('text', v) } updateRender () { this.innerText=this._text } } customElements.define('text-icon', TextIcon)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
调用方式1
<text-icon text="test1"></text-icon>
- 1
调用方式2
const textIcon = document.createElement('text-icon') textIcon.setAttribute('text', 'sddd') document.body.appendChild(textIcon) const textIcon1 = new TextIcon() textIcon1.text = 12 document.body.appendChild(textIcon1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 自定义元素升级
由于可以先创建一个元素,然后再定义该元素。然后再升级它
涉及API
window.customElements.upgrade(elementNode)
- 1
注意看代码中的注释
// 通过createElemnt创建自定义元素节点,注意此时text-icon 还定义const testUpgradeDom = document.createElement('text-icon')// 开始定义text-icon 元素class TextIcon extends HTMLElement { constructor() { super() this._text = null; } static observedAttributes = ['text']; attributeChangedCallback(name, oldValue, newValue) { this._text = newValue; this.updateRender(); } connectedCallback() { this.updateRender(); } get text () { return this._text; } set text(v) { console.log(v, 'ss') this.setAttribute('text', v) } updateRender () { this.innerText=this._text } } customElements.define('text-icon', TextIcon) // 此时我们可以看到testUpgradeDom 这个节点非自定义元素 console.log(testUpgradeDom instanceof TextIcon); //false // 调用升级api customElements.upgrade(testUpgradeDom) // 此时testUpgradeDom 就升级成了自定义元素创建的节点 console.assert(testUpgradeDom instanceof TextIcon); // true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 获取自定义元素构造器
customElements.get('text-icon')
- 1
- 监测自定义元素的定义
customElements.whenDefined('text-icon').then(function() { // 自定义操作 })
- 1
- 2
- 3
返回的是一个promise 当监测到text-icon 被定义后,我们可以去做一些操作,比如上面提到的升级操作等。
- attachInternals
我们都知道form内置标签能自动关联内置的表单元素的值。该功能让我们能够扩展更多的自定义表单元素。它核心需要处理的是通过internals暴露的属性和方法来实现和form之间的交互和值的关系。通过该api来获取form元素的一些内置属性,来让自定义元素能够和from元素一样来处理例如通过name 来获取值等表单的特点。
class MyCheckbox extends HTMLElement{ // 这个标示来控制是否是form关联的元素 static formAssociated = true static observedAttributes = ['checked']; constructor() { super() this._internals = this.attachInternals() this.addEventListener('click', this._onClick.bind(this)); } get form () { return this._internals.form; } get name () { return this._internals.name; } get type() { return this._internals.type } get checked () { return this.getAttribute('checked') } set checked(tag) { console.log(tag,'xx') this.toggleAttribute('checked', Boolean(tag)) } attributeChangedCallback() { this._internals.setFormValue(this.checked? 'on' : 'off') // this._internals.ariaChecked = this.checked; } _onClick (event) { debugger this.checked = !this.checked } } customElements.define('my-checkbox', MyCheckbox)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
调用
<form method="post" action=""> <label><my-checkbox name="agreed"></my-checkbox> I read the agreement.</label> <input type="submit"> </form>
- 1
- 2
- 3
- 4
- 一些生命周期
// 初始化constructor()// 组件第一次关联到文档connectedCallback()// 组件断开和文档的连接disconnectedCallback()// 组件关联到新的文档adoptedCallback()// 组件属性变化回调attributeChangedCallback()// 组件和表单关联的变化回调formAssociatedCallback()// 自定义关联表单组件的表单发生了reset操作的回调formResetCallback()// 自定义form元素 被设置为 disabled时的回调formDisabledCallback()// form restore时候触发formStateRestoreCallback()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
总结
几个注意点
- constructor里的super是必须的,否则就失去了this的关联属性
- 组件的命名要遵循规则
- 定义的类构造器中途不要出现return
- 构造器中不要出现document.write 或者window.open
- 在实际使用中,我们还可以将自定义元素 挂载在shadowdom上来做到类似沙箱一样的隔离能力