• Advanced Tooling for Web Components
  • Originally written by Caleb Williams
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Xuyuey
  • Proofread by: Long Xiong, Ziyin Feng

This series is made up of five articles, the first four of which provided a comprehensive introduction to the technologies that make up the Web Components standard. First, we looked at how to create HTML templates, paving the way for the rest of the work. Second, we took a closer look at custom element creation. Next, we wrap the element’s style and selector into the Shadow DOM so that our element is completely independent.

We explored the power of these tools by creating our own custom modal dialogs, which can be used in most modern application contexts, regardless of the underlying framework or library. In this article, we’ll show you how to use our elements in various frameworks and introduce some advanced tools to really improve the skills of Web Components.

Series of articles:

  1. Introduction of Web Components
  2. Write HTML templates that can be reused
  3. Create custom elements from 0
  4. Use Shadow DOM to encapsulate style and structure
  5. Advanced tools for Web Components (In this paper,)

The framework is compatible with

Our dialog box component works well in almost any framework. (Of course, if JavaScript is disabled, the whole thing is futile.) Angular and Vue treat Web Components as first-class citizens: The framework is designed with Web standards in mind. React is a little smug, but not impossible to integrate.

Angular

First, let’s look at how Angular handles custom elements. By default, Angular throws a template error whenever it encounters an unrecognized element (that is, a default browser element or any Angular defined component). You can change this behavior by including CUSTOM_ELEMENTS_SCHEMA.

. Allow ngModules to contain the following:

  • Non-angular elements use dashes (-Named after).
  • Element attributes are marked with dashes (-Named after). Dashes are a naming convention for custom elements.

– presents a document

Using this schema is as simple as adding it to a module:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @ngModule ({/** omit */ schemas: [CUSTOM_ELEMENTS_SCHEMA]})export class MyModuleAllowsCustomElements {}
Copy the code

Just like that. Angular will then allow us to use our custom elements wherever we use standard attributes and binding events:

<one-dialog [open]="isDialogOpen" (dialog-closed)="dialogClosed($event)">
  <span slot="heading">Heading text</span>
  <div>
    <p>Body copy</p>
  </div>
</one-dialog>
Copy the code

Vue

Vue is even more compatible with Web Components than Angular because it doesn’t require any special configuration. Once the element is registered, it can be used with Vue’s default template syntax:

<one-dialog v-bind:open="isDialogOpen" v-on:dialog-closed="dialogClosed">
  <span slot="heading">Heading text</span>
  <div>
    <p>Body copy</p>
  </div>
</one-dialog>
Copy the code

However, both Angular and Vue need to be aware of their default form controls. If we want to use something like a responsive form or Angular [(ng-Model)] or v-Model in Vue, we need to build pipes, which is beyond the scope of this article.

React

React is a little more complicated than Angular. React’s virtual DOM effectively takes a JSX tree and renders it as a large object. So instead of directly modifying attributes on HTML elements like Angular or Vue, React uses object syntax to keep track of changes that need to be made to the DOM and update them in bulk. This works fine in most cases. We bound the open property of the dialog box to the property of the object and responded very well when changing the property.

The problem occurs when we close the dialog and start scheduling the CustomEvent. React uses a synthetic event system to implement a series of native event listeners for us. Unfortunately, this means that control methods like onDialogClosed don’t actually attach event listeners to our component, so we have to find other ways.

The best known way to add custom event listeners in React is to use DOM Refs. In this model, we can refer directly to our HTML nodes. The syntax is a bit verbose, but it works:

import React, { Component, createRef } from 'react';

exportdefault class MyComponent extends Component { constructor(props) { super(props); // create a reference to this.dialog = createRef(); // Bind our method this.ondialogclosed = this.ondialogclosed. Bind (this); this.state = { open:false
    };
  }

  componentDidMount() {/ / component building is finished, add event listener. This dialog. The current. The addEventListener ('dialog-closed', this.onDialogClosed);
  }

  componentWillUnmount() {/ / uninstall the component, remove the listener. This dialog. The current. The removeEventListener ('dialog-closed', this.onDialogClosed);
  }

  onDialogClosed(event) { /** 省略 **/ }

  render() {
    return <div>
      <one-dialog open={this.state.open} ref={this.dialog}>
        <span slot="heading">Heading text</span>
        <div>
          <p>Body copy</p>
        </div>
      </one-dialog>
    </div>
  }
}
Copy the code

Alternatively, we can use stateless function components and hooks:

import React, { useState, useEffect, useRef } from 'react';

export default function MyComponent(props) {
  const [ dialogOpen, setDialogOpen ] = useState(false);
  const oneDialog = useRef(null);
  const onDialogClosed = event => console.log(event);

  useEffect(() => {
    oneDialog.current.addEventListener('dialog-closed', onDialogClosed);
    return () => oneDialog.current.removeEventListener('dialog-closed', onDialogClosed)
  });

  return <div>
      <button onClick={() => setDialogOpen(true)}>Open dialog</button>
      <one-dialog ref={oneDialog} open={dialogOpen}>
        <span slot="heading">Heading text</span>
        <div>
          <p>Body copy</p>
        </div>
      </one-dialog>
    </div>
}
Copy the code

This is fine, but you can see how reusing this component can quickly become cumbersome. Fortunately, we can export a default React component that wraps our custom elements with the same tools.

import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';

exportdefault class OneDialog extends Component { constructor(props) { super(props); // create a reference to this.dialog = createRef(); // Bind our method this.ondialogclosed = this.ondialogclosed. Bind (this); }componentDidMount() {/ / component building is finished, add event listener. This dialog. The current. The addEventListener ('dialog-closed', this.onDialogClosed);
  }

  componentWillUnmount() {/ / uninstall the component, remove the listener. This dialog. The current. The removeEventListener ('dialog-closed', this.onDialogClosed); } onDialogClosed(event) {// Check to make sure the property is present before calling itif(this.props.onDialogClosed) { this.props.onDialogClosed(event); }}render() { const { children, onDialogClosed, ... props } = this.props;return<one-dialog {... props} ref={this.dialog}> {children} </one-dialog> } } OneDialog.propTypes = { children: children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node ]).isRequired, onDialogClosed: PropTypes.func };Copy the code

. Or, again, use stateless function components and hooks:

import React, { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';

export default functionOneDialog(props) { const { children, onDialogClosed, ... restProps } = props; const oneDialog = useRef(null); useEffect(() => { onDialogClosed ? oneDialog.current.addEventListener('dialog-closed', onDialogClosed) : null;
    return () => {
      onDialogClosed ? oneDialog.current.removeEventListener('dialog-closed', onDialogClosed) : null;  
    };
  });

  return<one-dialog ref={oneDialog} {... restProps}>{children}</one-dialog> }Copy the code

Now we can use our dialogs in React and keep the same API in all of our applications (and without classes, if you prefer).

import React, { useState } from 'react';
import OneDialog from './OneDialog';

export default function MyComponent(props) {
  const [open, setOpen] = useState(false);
  return <div>
    <button onClick={() => setOpen(true)}>Open dialog</button>
    <OneDialog open={open} onDialogClosed={() => setOpen(false)}>
      <span slot="heading">Heading text</span>
      <div>
        <p>Body copy</p>
      </div>
    </OneDialog>
  </div>
}
Copy the code

Advanced tools

There are many great tools for writing your custom elements. A search on NPM reveals many tools for creating highly responsive custom elements (including my own pet project), but by far the most popular is lit-HTML from the Polymer team, or more specifically, LitElement in the case of Web Components.

LitElement is a custom element base class that provides a set of apis that can be used to do everything we’ve done so far. It can also run in a browser without building it, but can be used if you prefer to use more cutting-edge tools like decorators.

Before diving into how to use lit or LitElement, take a moment to familiarize yourself with tagged Template literals, a special function that calls template strings in JavaScript. These functions take an array of strings and a set of interpolations, and can return anything you want.

functiontag(strings, ... values) { console.log({ strings, values });return true;
}
const who = 'world';

tag`hello ${who}`; /** will print {strings: ['hello '.' '], values: ['world'}, and returnstrue* * /Copy the code

LitElement provides us with real-time, dynamic updates to whatever is passed to the array of values, so when the property is updated, the element’s render function is called and the DOM is re-rendered.

import { LitElement, html } from 'lit-element';

class SomeComponent {
  static get properties() {
    return { 
      now: { type: String }
    };
  }

  connectedCallback() {// Be sure to call super super.connectedCallback(); this.interval = window.setInterval(() => { this.now = Date.now(); }); }disconnectedCallback() {
    super.disconnectedCallback();
    window.clearInterval(this.interval);
  }

  render() {
    return html`<h1>It is ${this.now}</h1>`;
  }
}

customElements.define('some-component', SomeComponent);
Copy the code

See the LitElement example at CodePen.

You’ll notice that we must use the static Properties getter to define any properties that we want LitElement to monitor. Using this API tells the base class to call the Render function whenever a change is made to a component’s property. Render, in turn, updates only the nodes that need to be changed.

So, for our dialog example, it looks like this when using LitElement:

See a sample dialog box using LitElement at CodePen.

There are several lit-HTML variants available, including Haunted, a React hook library for Web Components, and you can also use lit-HTML as a base to use virtual Components.

Most modern Web Components tools today are in the style of LitElement: a base class that abstracts common logic from our Components. Other types are Stencil, SkateJS, Angular Elements and Polymer.

The next step

The Web Components standard continues to evolve as more and more new features are discussed and added to browsers. Soon, users of Web Components will have apis for advanced interaction with Web forms (including the internals of other elements beyond the scope of these introductory articles), such as native HTML and CSS module imports, native template instantiation and update controls, More can be tracked on GitHub at W3C/ Web Components Issues Board on GitHub.

These standards are ready to be applied to our project today and provide appropriate polyfills for older browsers and Edge. While they may not replace your chosen framework, they can be used together to enhance your and your team’s workflow.

Series of articles:

  1. Introduction of Web Components
  2. Write HTML templates that can be reused
  3. Create custom elements from 0
  4. Use Shadow DOM to encapsulate style and structure
  5. Advanced tools for Web Components (In this paper,)

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.