• A Guide to Custom Elements for React Developers
  • CHARLES PETERS
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Zi Fei
  • Proofread by: Xcco, Ivocin

Recently I needed to build UI interfaces, and while react.js is now my preferred UI solution, for the first time in a long time I didn’t use it. Then I looked at the browser’s built-in API and realized that using custom elements (i.e., Web components) might be just the solution the React developers need.

Custom elements can have roughly the same benefits as the React component and can be implemented without binding to a specific framework. Custom elements provide new HTML tags that can be manipulated programmatically using native browser apis.

Let’s talk about the benefits of component-based UI:

  • Encapsulation – Focuses on the internal implementation of components
  • Reuse – When you break up the UI into more generic pieces, they are easier to break down into forms you can reuse
  • Isolation – Because components are encapsulated, you get the added benefit of isolation, which makes it easier to locate errors and modify specific parts of your application

Use cases

You may be wondering who uses custom elements in production. Some of the more famous ones are:

  • GitHub uses custom elements for modal dialogs, autocomplete, and display times.
  • YouTube’s new Web application uses Polymer and Web components.

Similarities to component apis

When trying to compare the React component to the custom component, I found that their apis are very similar:

  • They are all classes, and classes are not new, and can be extended from base classes
  • They all inherit the mount or render life cycle
  • They both require props or Attributes to pass in data statically or dynamically

demo

So, let’s build a small application that provides a detailed list of GitHub repositories.

If I were to implement React, I would define a simple component like this:

<Repository name="charliewilco/obsidian" />
Copy the code

This component needs a prop — the repository name, which we implement like this:

class Repository extends React.Component {
  state = {
    repo: null
  };

  async getDetails(name) {
    return await fetch(`https://api.github.com/repos/${name}`, {
      mode: 'cors'
    }).then(res => res.json());
  }

  async componentDidMount() {
    const { name } = this.props;
    const repo = await this.getDetails(name);
    this.setState({ repo });
  }

  render() {
    const { repo } = this.state;

    if(! repo) {return <h1>Loading</h1>;
    }

    if (repo.message) {
      return <div className="Card Card--error">Error: {repo.message}</div>;
    }

    return (
      <div class="Card">
        <aside>
          <img
            width="48"
            height="48"
            class="Avatar"
            src={repo.owner.avatar_url}
            alt="Profile picture for ${repo.owner.login}"
          />
        </aside>
        <header>
          <h2 class="Card__title">{repo.full_name}</h2>
          <span class="Card__meta">{repo.description}</span> </header> </div> ); }}Copy the code

Check out Charles (@charlieWilco) ‘s React demo on CodePen — GitHub.

To take a closer look, we have a component that has its own state, the details of the repository. At the beginning, we set it to null because there is no data yet, so there will be a load prompt when the data is loaded.

In the React lifecycle, we use fetch to get data from GitHub, create tabs, and then use setState() to trigger a re-render once we get the returned data. All the different states used by the UI are shown in the Render () method.

Define/use custom elements

The implementation is slightly different with custom elements. Like the React component, our custom element also requires an attribute, the repository name, whose state is also self-managed.

Here are our elements:

<github-repo name="charliewilco/obsidian"></github-repo>
<github-repo name="charliewilco/level.css"></github-repo>
<github-repo name="charliewilco/react-branches"></github-repo>
<github-repo name="charliewilco/react-gluejar"></github-repo>
<github-repo name="charliewilco/dotfiles"></github-repo>
Copy the code

Check out Charles (@charlieWilco) ‘s demo of custom elements on CodePen – GitHub.

Now, all we need to do is define and register our customElements, create a class that inherits from the HTMLElement class, and then register the element’s name with customElements.define().

class OurCustomElement extends HTMLElement {}
window.customElements.define('our-element', OurCustomElement);
Copy the code

It is called like this:

<our-element></our-element>
Copy the code

This new element is not very useful yet, but with it, we can extend the functionality of this element in three ways. These methods are similar to the React component’s lifecycle API. The two class lifecycle functions that are most relevant to us are disconnectedCallBack and connectedCallback, and since a custom element is a class, it naturally has a constructor.

The name When to call
constructor Used to create or update an instance of an element. Used to initialize state, set event listeners, or create a Shadow DOM. If you want to know whereconstructorWhat can be done, please check the design specification.
connectedCallback Called after the element is inserted into the DOM. The code used to run creation tasks, such as fetching resources or rendering the UI. In general, you should try asynchronous tasks here.
disconnectedCallback Called after the element has been removed from the DOM. To run the code that does the cleanup.

To implement our custom element, we create the following classes and set uI-related attributes:

class Repository extends HTMLElement {
  constructor() {
    super();

    this.repoDetails = null;

    this.name = this.getAttribute("name");
    this.endpoint = `https://api.github.com/repos/${this.name}`    
    this.innerHTML = `<h1>Loading</h1>`
  }
}
Copy the code

By calling super() in our constructor, the element’s own context and DOM manipulation APIS are available. So far, we’ve set the default repository details to NULL, taken the repository name from the element attribute, created an endpoint to call so we don’t have to define it later, and most importantly, set the initial HTML to the load prompt.

To get details about the element repository, we’ll need to send a request to GitHub’s API. We use fetch, and since it’s Promise based, we use async and await to make our code easier to read. You can learn more about the async/await keyword here, and you can learn more about the browser fetch API here. You can also discuss it with me on Twitter to see if I prefer the Axios library. (Hint, it depends on whether I had tea or coffee with breakfast.)

Now, let’s add a method to this class to query GitHub for warehouse details.

class Repository extends HTMLElement {
  constructor() {/ /... } asyncgetDetails() {
    return await fetch(this.endpoint, { mode: "cors"}).then(res => res.json()); }}Copy the code

Next, let’s use the connectedCallback method and Shadow DOM to use the return value of the getDetails method. The effect of using this method and we React example invokes the Repository.com ponentDidMount similar (). We replace the null assigned to this.repoDetails at the beginning — and will use it later when the template is called to create the HTML.

class Repository extends HTMLElement {
  constructor() {/ /... } asyncgetDetails() {/ /... } asyncconnectedCallback() {
    let repo = await this.getDetails();
    this.repoDetails = repo;
    this.initShadowDOM();
  }

  initShadowDOM() {
    let shadowRoot = this.attachShadow({ mode: "open"}); shadowRoot.innerHTML = this.template; }}Copy the code

You’ll notice that we’re calling methods associated with Shadow DOM. In addition to being a title rejected by Marvel movies, Shadow DOM has its own rich API worth investigating. For our purposes, it abstracts out an implementation that adds innerHTML to an element.

Now we assign this. Template to innerHTML. Now define template:

class Repository extends HTMLElement {
  get template() { const repo = this.repoDetails; // If error information is obtained, a prompt message is displayed to the userif (repo.message) {
      return `<div class="Card Card--error">Error: ${repo.message}</div>`
    } else {
      return `
      <div class="Card">
        <aside>
          <img width="48" height="48" class="Avatar" src="${repo.owner.avatar_url}" alt="Profile picture for ${repo.owner.login}" />
        </aside>
        <header>
          <h2 class="Card__title">${repo.full_name}</h2>
          <span class="Card__meta">${repo.description}</span>
        </header>
      </div>
      `
    }
  }
}
Copy the code

Custom elements are pretty much like that. Custom elements manage their own state, retrieve their own data, and present their state to users, while providing HTML elements that can be used in applications.

After completing this exercise, I found that the only dependency required for custom elements is the browser’s native API and not another framework that needs to be parsed and executed. It’s a much more portable and reusable solution, and it’s very similar to the apis of the frameworks you like to work with for a living.

Of course, there are downsides to this approach, we’re talking about support issues across browsers and a lack of consistency. In addition, DOM manipulation apis can be confusing. Sometimes they are assignments. Sometimes they’re functions. Sometimes these methods require callbacks and sometimes they don’t. If you don’t believe me, look at the way document.createElement() is used to add classes to HTML elements. This is one of the top five reasons to use React. The basic implementation isn’t really complicated, but it’s not consistent with other similar Document methods.

The real question is: will it become obsolete? May be. React still does well at what it’s supposed to do: virtual DOM, managing application state, encapsulation, and passing data down the tree. There is little incentive to use custom elements in the framework right now. Custom elements, on the other hand, are very simple and useful for creating browser applications.

To learn more

  • Custom Elements V1: Reusable Web Components
  • Make native Web Components using Custom Elements v1 and Shadow Dom v1

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.