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

<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 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

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: hereonActionIs an arrow function because it needs to be boundthisPoint it to thisBookCardComponents.

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 ReactReact.createElementThe 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.jsThis 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.