I did a similar search on Nuggets after writing this article. It feels like my writing is just like anyone else’s. So I deleted it and rewrote it from a different Angle to solve a very important problem: how do I use the WebComponents component the same way I use the React component?

Why is it important? Here’s an example:

How do you add events if you use a normal WebComponents component? Something like this:

. counterDOM.addEventListener('myChange',function(e){ console.log("App -> e", e.detail.value); }) return (<my-counter max={10} min={-10} ref={counterDOM} ></my-counter>) ...Copy the code

To be honest, don’t you think it’s ugly? Who else is going to use a component like that?

What if that’s the case? Isn’t that much better?

<Counter
    onMyChange={(e) => {
       console.log("App -> e", e.detail.value);
     }}
     max={10}
     min={-10}
></Counter>
Copy the code

The GIF below shows how it looks in action:

Next we implement this component.

The implementation of my – counter

Instead of using the native WebComponents script, I used the Lit WebComponents library. Built on top of the Web Components standard, Lit is only 5KB compressed and renders very fast because Lit only touches the dynamic part of the UI when it updates — no need to rebuild the virtual tree and compare it to the DOM. And each Lit component is a native Web component.

Download Lit:

npm i lit
Copy the code

Next write our my-counter component:

import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; import { emit } from "./tool"; import styles from "./counter.style"; @customElement("my-counter") export default class MyCounter extends LitElement { static styles = styles; @property({ type: Number }) value = 0; @property({ type: Number }) min: number = -Infinity; @property({ type: Number }) max: number = Infinity; handleIncrease() { this.value = Math.min(this.value + 1, this.max); this.requestUpdate(); emit(this, "myChange", { detail: { value: this.value, }, }); } handleReduce() { this.value = Math.max(this.value - 1, this.min); this.requestUpdate(); emit(this, "myChange", { detail: { value: this.value, }, }); } render() { return html` <div class="counter-container"> <button class="button-left" @click=${this.handleReduce}>-</button> <p class="value-show">${this.value}</p> <button class="button-right" @click=${this.handleIncrease}>+</button> </div> `; } } declare global { namespace JSX { interface IntrinsicElements { "my-counter": any; }}}Copy the code

Writing style files:

import { css } from "lit";

export default css`
  .counter-container {
    box-sizing: border-box;
    width: 120px;
    height: 32px;
    opacity: 1;
    background: #ffffff;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 4px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    overflow: hidden;
  }
  .button-left,
  .button-right {
    outline: none;
    border: 0;
    width: 31px;
    height: 30px;
    cursor: pointer;
    background-color: white;
  }
  .button-left {
    border-right: 1px solid #ccc;
  }
  .button-right {
    border-left: 1px solid #ccc;
  }
  .value-show {
    margin: 0;
    width: 50px;
    text-align: center;
  }
`;

Copy the code

Now that we have written a WebComponents, we can use it:

const App = ()=>(<my-counter></my-counter>)
Copy the code

Let’s look at how to use it like the React component.

Convert to the React component

Let’s write a conversion function:

// Save the MAP of the event function const listenedEvents = new WeakMap(); Const addOrUpdateEventListener = (node, event, listener) => {let events = listenedEvents. // If (events === undefined) {listeneDevents.set (node, (events = new Map())); } // Let handler = events.get(event); if (listener ! If (handler === undefined) {// If (handler === undefined) {// If (handler === undefined) {// If (handler === undefined) listener })); node.addEventListener(event, handler); } else {// Update function handler.handleEvent = listener; } } else if (handler ! == undefined) {// delete events.delete(event); node.removeEventListener(event, handler); }}; const setRef = (ref, value) => { if (typeof ref === "function") { ref(value); } else { ref.current = value; }}; const setProperty = (node, name, value, old, Events) = > {/ / to get event functions const event events of = = = = null | | events = = = undefined? undefined : events[name]; if (event ! == undefined) {// Add a listener if (value! == old) { addOrUpdateEventListener(node, event, value); } } else { node[name] = value; }}; const conversionComponents = (React, tagName, elementClass, events) => { const { Component, createElement } = React; // Get Props passed to WebComponents, including events const classProps = new Set(object.keys (events! == null && events ! == void 0 ? events : {}) ); // Get Props passed to WebComponents, including the event for (const p in elementClass.prototype) {if (! (p in HTMLElement.prototype)) { classProps.add(p); } } class ReactComponent extends Component { constructor(props) { super(props); // Save a reference to DOM this.DOM = null; } _updateElement(oldProps) { if (this.DOM === null) { return; } // Pass the new Props value to the custom element, For (const prop in this.domprops) {setProperty(this.dom, prop, this.props[prop], oldProps? oldProps[prop] : undefined, events ); } } componentDidMount() { this._updateElement(); } componentDidUpdate(old) { this._updateElement(old); } render() {// get WebComponents DOM const userRef = this.props. __callback; if (this._ref === undefined || this._userRef ! == userRef) { this._ref = (value) => { if (this.DOM === null) { this.DOM = value; } if (userRef ! == null) { setRef(userRef, value); } this._userRef = userRef; }; } // Set the latest Props const Props = {ref: this._ref}; this.DOMProps = {}; for (const [k, v] of Object.entries(this.props)) { if (classProps.has(k)) { this.DOMProps[k] = v; } } return createElement(tagName, props); // act (props, Ref) => createElement(component, {... props, __forwardedRef: ref }, props === null || props === undefined ? undefined : props.children ) ); }; export default conversionComponents;Copy the code

The main idea of this conversion function is to convert a custom element to the React element through the createElement function, and then to transform the Props added to the element. If we pass an event function, we can add this function to the DOM of the custom element. So I don’t have to write it that ugly way.

The next step is to transform our WebComponents:

import React, { FC } from "react"; import conversionComponents from "./conversionComponents"; import MyCounter from "./counter"; interface PropCounter { max? : number; min? : number; onMyChange? : (e: { detail: { value: number } }) => void; } const Counter: FC<PropCounter> = conversionComponents( React, "my-counter", MyCounter, { onMyChange: "myChange", } ); export { Counter };Copy the code

Use:

import React, { useState } from "react";
import { Counter } from "./components";

function App() {
  const [num, setNum] = useState<number>(0);
  return (
    <div>
      <h1>Destiny__ {num}</h1>
      <Counter
        onMyChange={(e) => {
          console.log("App -> e", e.detail.value);
          setNum(e.detail.value);
        }}
        max={10}
        min={-10}
      ></Counter>
    </div>
  );
}

export default App;
Copy the code

After this conversion function, we can use the WebComponents as normal components!!

Those of you who are interested should try. If you have any questions, please leave a message below!! Answer at any time. If you don’t want to use Lit, you can do it native! Just rewrite the conversion function!!

🔥 Online try – over the wall

🔥 making address

Past wonderful

  • 🔥🔥🔥 How did I optimize the 50+MB APP package file to 4.2MB?
  • Can you explain the principle of Styleds – Components?
  • Interviewer: Can you talk about the difference between extends and a parasitic composite inheritance stereotype?
  • The front end Leader asked me to talk to my colleagues about the event loop
  • React compares the status update mechanism with Vue
  • Do you React to a tree shuttle box?
  • 10 Mistakes to avoid when Using React
  • Webpack Packaging Optimization Direction Guide (Theory)
  • What if VUE encounters drag-and-drop dynamically generated components?
  • JS Coding tips, most people can’t!!
  • TypeScript doesn’t it smell good? Come on!