preface
Hello, everyone. I’m a sea monster.
Recently, I wrote an article about jingdong micro front-end framework, “Preliminary Study of MicroApp, an extremely simple Micro front-end framework”, which mentioned something called Web Components. Although I have heard of it for a long time, I have not read it carefully, so I have a deep understanding of it, and today I will give you a simple share 🙂
The following practices are stored in the Github repository.
What is the
Web Components is actually a combination of a series of technologies, consisting of three main parts:
- Custom elements. Extend custom tag elements beyond the base HTML tags
- The Shadow of the DOM. It is used to isolate the content of the Shadow DOM from the outer Document DOM
- HTML templates.use
<template>
To define the component template, use<slot>
Used as a slot
Because it is a combination of apis, we should pay attention to the compatibility of these apis:
When the above techniques are properly used, functions and logic can be encapsulated in custom tags, and development efficiency can be improved by reusing these custom components. It sounds like vue.js and React, but it’s actually very similar when you use Web Components.
Get started
Let’s learn how to use it by implementing a
Web Component.
Custom elements
First, create an index. HTML and call the
component directly from there.
<body>
<book-card></book-card>
<script src="./BookCard.js"></script>
</body>
Copy the code
Since
is not recognized by browsers, we need to register it in bookcard.js and import and execute bookcard.js in index.html.
class BookCard extends HTMLElement {
constructor() {
super(a);const container = document.createElement('div')
container.className = 'container'
const image = document.createElement('img')
image.className = 'image'
image.src = "https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp"
const title = document.createElement('p')
title.className = 'title'
title.textContent = 'Prayers at Chernobyl.'
const desc = document.createElement('p')
desc.className = 'desc'
desc.textContent = 'S. A. Alexievich'
const price = document.createElement('p')
price.className = 'price'
price.textContent = RMB 25.00 ` `
container.append(image, title, desc, price)
this.appendChild(container)
}
}
customElements.define('book-card', BookCard)
Copy the code
The basic DOM structure is already implemented:
Does executing createElement and setting the property look a bit like React. CreateElement?
HTML template
Generating DOM structures line by line like this is not only hard to write, but also hard to read. To solve this problem, we can use HTML templates. Write a
template directly in HTML:
<body>
<template id="book-card-template">
<div class="container">
<img class="image" src="https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp" alt="">
<p class="title">The memorial service at Chernobyl</p>
<p class="desc">S. A. Alexievich</p>
<p class="price">RMB 25.00</p>
</div>
</template>
<book-card></book-card>
<script src="BookCard.js"></script>
</body>
Copy the code
Then call this template directly from the registry component:
class BookCard extends HTMLElement {
constructor() {
super(a);const templateElem = document.getElementById('book-card-template')
const clonedElem = templateElem.content.cloneNode(true)
this.appendChild(clonedElem)
}
}
customElements.define('book-card', BookCard)
Copy the code
Does this
writing look a bit like the familiar Vue framework?
Writing style
Now that we’re done with the DOM, we’re ready to write styles. Add a new
<body>
<template id="book-card-template">
<style>
p { margin: 0 }
.container { display: inline-flex; flex-direction: column; border-radius: 6px; border: 1px solid silver; padding: 16px; margin-right: 16px }
.image { border-radius: 6px; }
.title { font-weight: 500; font-size: 16px; line-height: 22px; color: # 222; margin-top: 14px; margin-bottom: 9px; }
.desc { margin-bottom: 12px; line-height: 1; color: #8590a6; font-size: 14px; }
.price { color: #8590a6; font-size: 14px; }
</style>
<div class="container">
<img class="image" src="https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp" alt="">
<p class="title">The memorial service at Chernobyl</p>
<p class="desc">S. A. Alexievich</p>
<p class="price">RMB 25.00</p>
</div>
</template>
<book-card></book-card>
<script src="BookCard.js"></script>
</body>
Copy the code
I’m sure everyone would write CSS like this, so I folded CSS to reduce the length, and the result is as follows:
Shadow DOM
To avoid collisions between the
class BookCard extends HTMLElement {
constructor() {
super(a);this.attachShadow({ mode: 'open' })
const templateElem = document.getElementById('book-card-template')
const clonedElem = templateElem.content.cloneNode(true)
this.shadowRoot.appendChild(clonedElem)
}
}
customElements.define('book-card', BookCard)
Copy the code
If you open the console, you can see that the
DOM is attached to Shadow Root:
The entire DOM architecture looks like this:
One of the strengths of the Shadow DOM is its ability to separate DOM structure, style, and behavior from the Document DOM, which makes it a great way to encapsulate components, making it an important part of Web Components.
Shadow DOM is also used in our daily development. For example, the controls DOM structure in the
Props
As with the Vue and React components, we can also pass properties in the Web Component:
<body>
<template id="book-card-template">
<style>.</style>
<div class="container">
<img class="image" src="" alt="">
<p class="title"></p>
<p class="desc"></p>
<p class="price"></p>
</div>
</template>
<book-card
data-image="https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp"
data-title="Prayers at Chernobyl."
data-desc="S.a. Alexievich."
data-price="25.00"
></book-card>
<script src="BookCard.js"></script>
</body>
Copy the code
These attributes are then updated to the DOM:
const prefix = 'data-'
class BookCard extends HTMLElement {
constructor() {
super(a);this.attachShadow({ mode: 'open' })
const templateElem = document.getElementById('book-card-template')
const clonedElem = templateElem.content.cloneNode(true)
clonedElem.querySelector('.container > .image').src = this.getAttribute(`${prefix}image`)
clonedElem.querySelector('.container > .title').textContent = this.getAttribute(`${prefix}title`)
clonedElem.querySelector('.container > .desc').textContent = this.getAttribute(`${prefix}desc`)
clonedElem.querySelector('.container > .price').textContent = ` RMBThe ${this.getAttribute(`${prefix}price`)}`
this.shadowRoot.appendChild(clonedElem)
}
}
customElements.define('book-card', BookCard)
Copy the code
In general, we use data-xxx for custom attributes.
If you can think further, you can take attributes directly and use them for Proxy, reactive assignment, and so on. MMM, it smells like Vue!
const prefix = 'data-'
const attrList = Array.from(this.attributes);
const props = attrList.filter(attr= > attr.name.startsWith(prefix))
/ / to monitor props
watch(props)
Copy the code
Slot
Another benefit of HTML templates is that you can use
just like Vue. For example, now we can add an action slot at the bottom of the
:
<template id="book-card-template">
<style>.</style>
<div class="container">
<img class="image" src="" alt="">
<p class="title"></p>
<p class="desc"></p>
<p class="price"></p>
<div class="action">
<slot name="action-btn"></slot>
</div>
</div>
</template>
Copy the code
When others want to use the
component, they can inject their own custom content via slots:
<book-card
data-image="https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp"
data-title="Prayers at Chernobyl."
data-desc="S.a. Alexievich."
data-price="25.00"
>
<button slot="action-btn" class="btn primary">buy</button>
</book-card>
<book-card
data-image="https://pic1.zhimg.com/50/v2-a6d65e05ec8db74369f3a7c0073a227a_200x0.webp"
data-title="Prayers at Chernobyl."
data-desc="S.a. Alexievich."
data-price="25.00"
>
<button slot="action-btn" class="btn danger">delete</button>
</book-card>
Copy the code
interaction
We can also bind events to elements like React, such as adding click events to action
:
class BookCard extends HTMLElement {
constructor() {
super(a);this.attachShadow({ mode: 'open' })
const templateElem = document.getElementById('book-card-template')
const clonedElem = templateElem.content.cloneNode(true)... clonedElem.querySelector('.container > .action').addEventListener('click'.this.onAction)
this.shadowRoot.appendChild(clonedElem)
}
onAction = () = > {
alert('Hello World')}}Copy the code
Note: hereonAction
Is an arrow function because it needs to be boundthis
Point it to thisBookCard
Components.
UI library
Some of you may already be wondering: If this thing can be used to encapsulate components, is there a Corresponding Web Component UI library? The answer is yes.
At present, the more famous Microsoft FAST:
There’s Google Lit:
There is my factory Tencent OMI:
conclusion
Here are some ways to use Web Component. In summary, Web Component is a combination of apis:
- Custom Element: Registers and uses components
- Shadow DOM: isolates the CSS
- HTML Template and Slot: Flexible DOM structures
The Web Component is pretty much finished, so I don’t know how you feel about it. It seems to me that native componentization encapsulation is provided, but there are many things left undone, such as what we would like to see:
- Tracking data in a responsive manner like Vue
- Template syntax like Vue, data binding
- DOM Diff like React
- Like React
React.createElement
The JSX writing - .
None of these “framework features” that we want for Web Components are available. This is because the content of Web Components is made up of apis, and these apis are intended as specifications to be single-functional and orthogonal, rather than component-based “frameworks” like Vue and React. This is zhihu’s Web Component and React, Angular, Vue componentization Technologies: Who will be the Future? The answer:
The framework’s job is to provide a complete set of solutions, and the platform API’s design requirement is not to provide a complete set of solutions (i.e., zero coupling, orthogonal), which is a fundamental contradiction that cannot be reconciled.
The main benefit of Web Component is native support without external dependencies. aindex.html
+ main.js
This completes component registration and usage.
It is still evolving and can be used in production environments such as the Single-SPA Layout Engine and MicroApp, Another scenario is that you can use Web Component encapsulation in functional components such as TextArea and Video.