Web Component is an official custom component implementation that allows developers to implement custom page components without relying on any third-party frameworks (e.g., Vue, React). Achieve component reuse effect.
How to implement custom components using WebComponent
Next, I will implement a simple custom component step by step that contains a button and UL list that will be dynamically added by clicking the button.
So let’s look at the end result
Define and use the Web Component trilogy
-
Inherit HTMLElement to implement a custom class
class MyButton extends HTMLElement { constructor() { super(); } } Copy the code
-
Defining component templates
<template> </template> Copy the code
-
Register custom elements
window.customElements.define(name, ComponentClass) Copy the code
To complete a simple custom component
-
Define custom components
class MyButton extends HTMLElement { constructor () { super(a);const template = document.getElementById('mybutton'); const content = template.content.cloneNode(true); this.appendChild(content); }}Copy the code
-
Defining component templates
<template id="mybutton"> <button>Add</button> </template> Copy the code
-
Certified components
window.customElements.define('my-button', MyButton); Copy the code
-
Using the component
<body> <my-button></my-button> </body> Copy the code
With these four steps, you can define a custom component and successfully render it to the page.
The end result is as follows
Sum up the knowledge of this section
- Inherit the HTMLElement class using the ES6 class, and call super() in the subclass’s constructor, where you get the template content and add it to the current instance
- Define the component template, using the
tag
- Through the global API
window.customElements.define()
Register a custom component, otherwise the component will not be used - Use custom components just like normal tag elements
One thing need to be aware of, const content = template. The content. cloneNode (true); This line of code makes a deep copy of a template, because the template can be used in multiple places, and if you use the template itself directly, it will affect each other.
Here is the complete code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Component</title>
</head>
<body>
<my-button></my-button>
<template id="mybutton">
<button>Add</button>
</template>
</body>
<script>
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
this.appendChild(content); }}window.customElements.define('my-button', MyButton)
</script>
</html>
Copy the code
Next we will refine and enrich the custom component step by step.
Add styles to components
We only defined a template skeleton for the component above. We did not use CSS to decorate our component. We can add styles to the component in the template.
<style>
button {
width: 60px;
height: 30px;
cursor: pointer;
color: blue;
border: 0;
border-radius: 5px;
background-color: #F0F0F0;
}
</style>
<template>
<button>
Add
</button>
</template>
Copy the code
Add a custom button after styling
How do I receive external parameters?
A component with only the above capabilities will have poor reusability because it cannot change dynamically based on parameters; Webcomponents can receive external parameters that developers can use to manipulate and render components differently.
- Add attributes to the custom component that need to be passed
<body>
<my-button
text="Hello"
></my-button>
</body>
Copy the code
- Get property values from custom component classes and use them as needed
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
+ const text = this.getAttribute('text');
+ content.querySelector('#btn').innerText = text;
this.appendChild(content); }}Copy the code
Attributes added to the component can be obtained through this.getAttribute(name). We can add as many attributes to the component as we want.
The final render will look like this
How do I send parameters to the outside world?
If a component needs to interact with the outside world, it needs to be able to send data to the outside world.
There are two ways to do this: 1) add custom methods to the custom component, listen for DOM element events within the component, and call the custom methods of the component where needed; 2) Use the element’s custom events
1) Add custom methods
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
const button = content.querySelector('#btn');
const text = this.getAttribute('text');
button.innerText = text;
+ button.addEventListener('click'.(evt) = >{+this.onClick("Hello from within the Custom Element"); +})this.appendChild(content); }}Copy the code
document.querySelector('my-button').onClick = value= > {
console.log(value);
}
Copy the code
2) Use elements to customize events
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
const button = content.querySelector('#btn');
const text = this.getAttribute('text');
button.innerText = text;
button.addEventListener('click'.(evt) = >{+this.dispatchEvent(
+ new CustomEvent('onClick', {
+ detail: 'Hello fom within the Custom Element'+}})))this.appendChild(content); }} +document.querySelector('my-button').addEventListener('onClick'.(value) = >
+ console.log(value));
Copy the code
Native slots can be defined just like Vue slot slots
Now consider the following: can slots be used in webComponent like slots in Vue? It is also possible. In fact, Vue references the webComponent specification for slot implementation. Let’s see how slot can be used in a Web Component.
The first step is to define a slot label slot placeholder in the template, and the slot can add default display content
<template id="mybutton">
<style>
button {
color:blue;
border: 0;
border-radius: 5px;
background-color: brown;
}
</style>
<button id="btn">My Button</button>
+ <p><slot name="my-text">My Default Text</slot></p>
</template>
Copy the code
Then, when using the component, add a slot property to the content, specifying the slot to be filled in
<my-button>
+ <p>Another Text from outside</p>
</my-button>
Copy the code
The final rendering looks like this
Listen for property changes
In “How do I receive external parameters?” In the first section, we know how to pass data to components. If the data passed in changes, we expect the component content to change as well, as with Vue data binding; Similar functionality can be implemented in the Web Component.
Let’s start with three new functions
static get observedAttributes()
attributeChangedCallback(name, oldVal, newVal)
render()
The first function is the static GET function, which defines which properties to listen for;
The second function is a callback for property changes. That is, this function is called whenever the value of a monitored property changes.
The third function is the render function, which is called to re-render the component after the property is updated.
Here’s how to listen for properties and update components (all of the following functions are added to the custom component class, which is the MyButton class for this example)
- Define the properties to listen on
static get observedAttributes() {
return ['text'];
}
Copy the code
- Define the callback function
attributeChangedCallback(name, oldVal, newVal) {
this[name] = newVal;
this.render();
}
Copy the code
- To render
render() {
this.$button.innerText = this.text;
}
Copy the code
This.$button needs to be defined in the constructor
The complete class definition is as follows
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
const button = content.querySelector('#btn');
const text = this.getAttribute('text');
+ this.$button = button;
button.innerText = text;
button.addEventListener('click'.(evt) = > {
this.dispatchEvent(
new CustomEvent('onClick', {
detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content);
}
+ static get observedAttributes() {+return ['text']; +},attributeChangedCallback(name, oldVal, newVal){+this[name] = newVal;
+ this.render(); +},render(){+this.$button.innerText = this.text; +}}Copy the code
Property values on custom components can be dynamically changed through code, as simulated below in Chrome DevTool
Define the set and get functions for attributes
In the above example, there are two inconvenient points: 1) Each time an attribute is updated, it needs to be manually assigned in the attributeChangedCallback function; 2) Attributes can only be added to DOM elements; We want to be able to change property values directly in JavaScript code as if we were assigning values to objects.
Add a GET function to each attribute that needs to be listened on
get text () {
return this.getAttribute('text');
}
Copy the code
Redefine the set function of the property
set text (value) {
this.setAttribute('text', value);
}
Copy the code
We can now define attributes and assign values to component objects directly, rather than defining them on component DOM elements. The following
const mybutton = document.querySelector('my-button');
mybutton.text = 'Hi! ';
Copy the code
The life cycle
In addition to the above, we need to understand the Life cycle of a Web Component, which allows us to do what we need to do at different times in our custom component.
connectedCallback
This callback is called when the Web Component is added to the DOM and is executed only once. You can do some initialization in this callback, such as parameterizing the component’s style.
disconnectedCallback
Executed when the Web Component is removed from the document DOM.
adoptedCallback
Executed when the Web Component is moved to a new document.
attributeChangedCallback
Executed when the monitored property changes.
Below is the complete code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
</head>
<body>
<my-button>
<p slot="my-text">Another text from outside</p>
</my-button>
<template id="mybutton">
<style>
button {
width: 60px;
height: 30px;
cursor: pointer;
color: blue;
border: 0;
border-radius: 5px;
background-color: #F0F0F0;
}
</style>
<div>
<button id="btn">Add</button>
<p><slot name="my-text">My Default Text</slot></p>
</div>
</template>
</body>
<script>
class MyButton extends HTMLElement {
constructor () {
super(a)const template = document.getElementById('mybutton');
const content = template.content.cloneNode(true);
const button = content.querySelector('#btn');
this.$button = button;
button.addEventListener('click'.(evt) = > {
this.dispatchEvent(
new CustomEvent('onClick', {
detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content);
}
get text () {
return this.getAttribute('text');
}
set text (value) {
this.setAttribute('text', value);
}
static get observedAttributes() {
return ['text'];
}
attributeChangedCallback(name, oldVal, newVal) {
this.render();
}
render() {
this.$button.innerText = this.text; }}const mybutton = document.querySelector('my-button');
mybutton.addEventListener('onClick'.(value) = > {
console.log(value)
mybutton.text = value
});
window.customElements.define('my-button', MyButton)
</script>
</html>
Copy the code
Stop there?
We’ve covered the basics of Web Component; However, we define the components directly in THE HTML document, so in the actual project can not achieve reuse effect, because we can not directly put the code on someone else’s page, but through the way of reference to call, so we want to implement modular components.
A Web Component can be defined in a JS file and referenced by others via the script tag. Let’s change the previous component.
Define a component template using a template string, write a custom component class, register, and create a new myButton.js file
The complete code is shown below
const template = document.createElement('template');
template.innerHTML = `
My Default Text
`;
const Texts = [
'My lady, Hello! '.'BuiBuiBui'.'BiliBili'.'Haiwei is NO.1'
]
class MyButton extends HTMLElement {
constructor () {
super(a)const content = template.content.cloneNode(true);
const button = content.querySelector('#btn');
const textList = content.querySelector('#text-list');
this.$button = button;
this.$message = content.querySelector('#message');
button.addEventListener('click'.(evt) = > {
const li = document.createElement('li');
li.innerText = Texts[Math.floor(Math.random() * 4)];
textList.appendChild(li);
this.dispatchEvent(
new CustomEvent('onClick', {
detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content);
}
get text () {
return this.getAttribute('text');
}
set text (value) {
this.setAttribute('text', value);
}
static get observedAttributes() {
return ['text'];
}
attributeChangedCallback(name, oldVal, newVal) {
this.render();
}
render() {
this.$message.innerText = this.text; }}window.customElements.define('my-button', MyButton)
Copy the code
Called in the document
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
<script src="./MyButton.js"></script>
</head>
<body>
<my-button>
<p slot="my-text">Another text from outside</p>
</my-button>
</body>
<script>
const mybutton = document.querySelector('my-button');
mybutton.addEventListener('onClick'.(value) = > {
console.log(value)
mybutton.text = value.detail
});
</script>
</html>
Copy the code
At this point, we have a Reusable Web Component.