In fact, the specification of Web Components has been proposed for many years, but it depends on the browser’s bottom implementation, as well as the ecological development, compared with other JS framework, has not been popular, especially in China, more used abroad. Did some research recently, just in case. After all, programmers are always learning… Come out with more or less understanding of the technology.
I have written a series of three articles, which will be updated in succession:
- Web Components Series I – Basic Concepts
- Web Components series 2 – Integration with existing front-end frameworks
- Web Components series (Part 3) – Introduction to relevant Open Source Libraries (Stencil and Lit)
All right, now for the formal introduction.
concept
Front-end frameworks like React and Vue have the concept of componentize development, but they all rely on a single runtime code. Can browsers support componentization from the ground up? The Web specification committee has come up with some specifications that enable browsers to natively support componentization. Thus, for browser vendors, the need to implement these specifications is a matter of calling some API for programmers.
It mainly includes the following three specifications:
- Custom Elements (standard HTML specification)
Create custom HTML tags using a set of JS apis that extend existing built-in HTML tags. It also provides lifecycle hook functions such as VUE and React, which can perform some operations at the appropriate time.
- Shadow DOM (standard DOM specification)
A set of JS apis enables you to create a shadow DOM tree that can be inserted into the current HTML document. CSS and DOM scoping are provided. The CSS and DOM inside the Shadow DOM Tree are completely isolated from the rest of the document, so the interior can also use more general CSS selectors without worrying about name conflicts and global contamination.
In addition, elements in shadow root cannot be obtained by document.querySelector, but only by obtaining shadowRoot, the root node of shadow DOM tree, and accessing it by Shadowroot. querySelector.
As you can see, the main purpose of the Shadow DOM is to provide encapsulation.
Note: The content in the document scope is called the Light DOM, and the content in the shadow root is called the shadow DOM.
- Templates (standard HTML specifications)
Use
and
to define reusable structures directly in an HTML file, which will not be rendered in the document but can be accessed via the DOM API:
<template id="myTpl">
<p class="name">Name</p>
<slot></slot>
</template>
Copy the code
These three specifications do not have to be used together, but together they better encapsulate a custom component.
Compatibility and application scenarios
- Custom Elements
- Shadow DOM
- HTML Templates
Use PolyFills: WebComponents/Polyfills for incompatible browsers, and support extends to IE11.
Because Web Components rely on the browser’s underlying implementation and are not tied to a specific front-end framework, such as Vue, React, Angular, etc., they are suitable for UI components that need to be shared across teams, which are developed using Web Components. There is no need to limit the type of framework that the Consumer can use.
In addition, the ecological development of Vue, React and so on is relatively complete, providing peripheral areas such as routing, state storage and so on. The development efficiency is also higher than that of using native Web Components. Therefore, Web Components basically need to be used in conjunction with the front-end framework rather than replacing these popular front-end frameworks.
The specific use
This section is relatively detailed, so if you don’t want to get bogged down in details at first, scroll down to the bottom to see a complete example of a Button component. Git Portal: My-Web-Component (Git portal: My-Web-Component)
Custom elements
Register custom elements
customElements.define(tagName, constructor.options)
Copy the code
- tagName: Component name, using the Kebab-Case rule, and must be included
-
. - constructor: Defines a component. It is defined as an ES6 class, inheriting existing HTML tags if they do not need to be extended
HTMLElement
To ensure that it has all the default methods and attributes of the built-in HTML tag.
class MyButton extends HTMLElement {
constructor() {
super(a)// do something
}
}
customElements.define('my-button', MyButton)
Copy the code
- options: Optional. Currently, there is only one option
extends
When the created element needs to inherit from an existing built-in element. At this time needextends
Specific tag classes, such asHTMLButtonElement
.
class MyButton extends HTMLButtonElement {
consotructor() {
super(a)// do something
}
}
customElements.define('my-button', MyButton, {extends: 'button'})
Copy the code
Using a custom component that inherits from the built-in element:
Use in HTML:
<button is="my-button">click</button>
Copy the code
Use in js:
document.createElement('button', {is: 'my-button'})
Copy the code
Lifecycle hook
- connectedCallback
Called when the Custom Element is first inserted into the document DOM.
- disconnectedCallback
Called when the Custom Element is removed from the document DOM.
- adoptedCallback
Called when the Custom Element is moved to a new document. (e.g. between Iframe and main window)
- attributeChangedCallback
Called when attributes on a Custom element are added, modified, or removed. (attributeChangedCallback is called once when the user first uses the tag and passes in the attribute.)
Usage:
// 1. The static method returns the name of the attributes to be observed
static get observedAttributes() {return ['attr1'.'attr2']; }
// 2. Fires the hook when the attributes observed change
attributeChangedCallback(attrName, oldValue, newValue) {
// do something
}
Copy the code
Note: Attributes only accepts strings. When you need to pass in an Object type (Object, Array), either json. stringify, or get the element through JS and set the value to property. The second is recommended, especially when passing in function. (The difference between attribute and property is not detailed here, the former is Html level, the latter is JS level)
// Pass data of reference type through property
const myDropDown = document.querySelector('my-dropdown');
myDropDown.data = [
{
label: 'singing'.value: 'singing'
},
{
label: 'swimming'.value: 'swimming'
}];
// String types are usually passed directly on the HTML tag, but can also be passed through the property
const myButton = document.querySelector('my-button');
myButton.label = 'Click Me! ';
Copy the code
Since attributeChangedCallback cannot listen for changes to the element property, we need to synchronize the attribute through the setter:
set label(value) {
this.setAttribute('label', value);
}
Copy the code
If the data is of a reference type, there is no need to synchronize to the attribute at all. You can do things like update rendering directly in the setter rather than throughattributeChangedCallback
.
Shadow DOM
Mount the shadow of the Dom
Create a Shadow DOM inside the component:
const shadowRoot = element.attachShadow({mode: 'open'})
Copy the code
This method mounts a Shadow DOM Tree inside the custom element and returns a reference to ShadowRoot.
Example:
// Get the page
const $root = document.getElementById('root');
const shadow = $root.attachShadow({mode: 'open'});
const $p = document.createElement('p');
$p.innerText = 'Create a shadow node';
shadow.appendChild($p);
Copy the code
- element
It can be any custom tags or existing HTML tags that are allowed. MDN element.attachShadow () MDN element.attachShadow ()
ShadowRoot can add child nodes just like any other DOM node by calling the appendChild method.
- mode
There are two values, which are mainly used to control whether the external shadowRoot can be accessed by the shadow DOM tree (such as add or modify) :
- Open: indicates that JS code outside the shadowDOM subtree can access its internal elements, such as through
element.shadowRoot
The shadowRoot reference can be obtained. - Closed: Vice versa. When calling
element.shadowRoot
When to return tonull
.
- shadowRoot
Is the root node of the Shadow DOM tree. The whole tree will be rendered separately from the original node tree. ShadowRoot inherits Node and thus has the properties and methods of a normal htmlNode Node. In addition, there are some properties and methods of their own: MDN ShadowRoot
HTML template
Vue provides component templates, slot, and other functions, you can specify the name of the slot to become a named slot, Vue students should be familiar with the use of the slot is the same as Vue.
Use the JS API to get the content of the template and insert it into the Shadow DOM tree:
const templateContent = document.querySelector('#tpl-id').content
// cloneNode is required when adding template contents to nodes because templates are reusable
const shadowRoot = this.attachShadow({mode: 'open'})
shdowRoot.appendChild(templateContent.cloneNode(true))
Copy the code
Complete Button component example
Here is a simple example of customizing a button element:
const myButtonTemplate = document.createElement('template');
myButtonTemplate.innerHTML = `
`;
class MyButton extends HTMLElement {
constructor() {
super(a);this._shadowRoot = this.attachShadow({ mode: 'open' });
this._shadowRoot.appendChild(myButtonTemplate.content.cloneNode(true));
this.$button = this._shadowRoot.querySelector('button')
// dispatch custom event
this.$button.addEventListener('click'.() = > {
this.dispatchEvent(new CustomEvent('clicked', {
detail: {
clickMessage: 'Hello from within the Custom Element'}}}})))// Reflecting an attribute to a property
get label() {
return this.getAttribute('label')}// Reflecting a property to an attribute
set label(value) {
this.setAttribute('label', value)
}
static get observedAttributes() {
return ['label']}attributeChangedCallback(name, oldValue, newValue) {
this.render()
}
render() {
this.$button.innerHTML = this.label
}
}
window.customElements.define('my-button', MyButton);
Copy the code
Use in HTML:
<body>
<my-button label="Click Me!"></my-button>
<script src="./components/MyButton.js"></script>
<script>
document.querySelector('my-button').addEventListener('onClick'.(event) = > {
console.log('event', event)
})
</script>
</body>
Copy the code
Git Portal: my-web-Component
More complete code is uploaded here, as well as Dialog and Dropdown components, where the Dropdown component shows how to handle data of reference types.
reference
- Web Components Tutorial for Beginners
- web components best practices
- Web Components Introduction example tutorial – Ruan Yifeng
- MDN uses Custom Elements