3
头图
React is familiar to everyone, and WebComponents is probably also heard. So what kind of sparks will React+WebComponents collide?

In fact, there is such an open source framework that supports the React way to write components, and the final packaged product is WebComponents.

It's direflow, a framework that supports writing WebComponents in React.

Framework address: https://github.com/Silind-Software/direflow

Why choose direflow

There are many WebComponents frameworks in the open source community, such as stencil, lit, etc. These framework communities are highly active and have many implementation practices, but they all have some problems that do not fit our scenario:

  • Each has its own set of DSL, which has a certain learning cost
  • Lack of best practices based on React stacks like AntD

The direflow based on React can perfectly avoid the above problems.

  • React way to write components, zero learning cost increase
  • Based on Webpack packaging, React technology stacks such as AntD can be used directly

landing analysis

We have implemented WebComponents for a while. At present, we have implemented several scenarios, which greatly improved the development efficiency and greatly reduced the cost of business line access. Therefore, we will make a summary in stages.

direflow-out.png

Suppose there is such a web component.

 <test-component name="jack" age="18" />

Principle analysis

Complete build steps

A complete direflow web component with the following steps.

  1. Create a web component tag
  2. Create a React component, convert attributes into properties and pass it into the React component (hijacking through Object.defineProperty, and attribute real-time refresh through attributeChangedCallback)
  3. Mount this React application to the shadowRoot of the web component

<img width="592" alt="direflow" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7062464f9cfb469f9b92f63c471108c3~tplv-k3u1fbpfcp-zoom-1.image">

Let's analyze it in detail:

Suppose direflow is configured as follows:

 import { DireflowComponent } from "direflow-component";
import  App from "./app";

export default DireflowComponent.create({
  component: App,
  configuration: {
    tagname: "test-component",
    useShadow: true,
  },
});

Create a web component

 const WebComponent = new WebComponentFactory(
  componentProperties,
  component,
  shadow,
  anonymousSlot,
  plugins,
  callback,
).create();

customElements.define(tagName, WebComponent);

Declare a web component through customElements.define, the tagName is "test-component", and WebComponent is an instance of the web components factory function that integrates the ability to render react components.

web components factory function

Responsive

Hijack all properties.

 public subscribeToProperties() {
  const propertyMap = {} as PropertyDescriptorMap;
  Object.keys(this.initialProperties).forEach((key: string) => {
    propertyMap[key] = {
      configurable: true,
      enumerable: true,

      set: (newValue: unknown) => {
        const oldValue = this.properties.hasOwnProperty(key)
          ? this.properties[key]
          : this.initialProperties[key];

        this.propertyChangedCallback(key, oldValue, newValue);
      },
    };
  });

  Object.defineProperties(this, propertyMap);
}

First, convert attributes to properties.
Second, hijack properties through Object.defineProperties, and in the setter, trigger the propertyChangedCallback function.

 const componentProperties = {
  ...componentConfig?.properties,
  ...component.properties,
  ...component.defaultProps,
};

When the property in the above code changes, remount the React component. (This is generally for the development environment to get the latest view)

 /**
 * When a property is changed, this callback function is called.
 */
public propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown) {
  this.properties[name] = newValue;
  this.mountReactApp();
}

When the attribute changes, the component is remounted, and the native attributeChangedCallback of the web component is triggered at this time.

 public attributeChangedCallback(name: string, oldValue: string, newValue: string) {
  const propertyName = factory.componentAttributes[name].property;
  this.properties[propertyName] = getSerialized(newValue);
  this.mountReactApp();
}

Create a React component

Corresponds to this.mountReactApp above.

 <EventProvider>
  {React.createElement(factory.rootComponent, this.reactProps(), anonymousSlot)}
<EventProvider>
  • EventProvider - Creates an Event Context wrapped component for web components to communicate with the outside world.
  • factory.rootComponent - Pass in the component of DireflowComponent.create as the root component and create it through React.createElement.
  • this.reactProps() - get serialized properties. Why do you need to serialize, because the attribute of the html tag only accepts the string type. Therefore, you need to serialize the passed value through JSON.stringify(), and JSON.parse will be done inside the factory function. Convert attribute to property
  • anonymousSlot - anonymous slot, slot. Content can be distributed directly inside web component tags.

Mount the React application to the web component

 const root = createProxyRoot(this, shadowChildren);
ReactDOM.render(<root.open>{applicationWithPlugins}</root.open>, this);

Proxy components use React components as children, and ReactDOM renders the proxy components.

Create a proxy component

It is mainly to mount the React component after Web Component to the shadowRoot of the web component.

 const createProxyComponent = (options: IComponentOptions) => {
  const ShadowRoot: FC<IShadowComponent> = (props) => {
    const shadowedRoot = options.webComponent.shadowRoot
      || options.webComponent.attachShadow({ mode: options.mode });

    options.shadowChildren.forEach((child) => {
      shadowedRoot.appendChild(child);
    });

    return <Portal targetElement={shadowedRoot}>{props.children}</Portal>;
  };

  return ShadowRoot;
};

Obtain the shadowRoot, if not, attachShadow creates a new shadow root.
Add child nodes to shadow root.
Returns a Portal that mounts components to the shadow root, and receives higher-order functions for children.

think

Why do you need to remount React App for every attribute change? Can't you think of it as a regular react component, using react's own refresh capabilities?

Because the final product of direflow is a web component.
When an attribute changes, react cannot automatically perceive this change, so it is necessary to remount the React App by listening for attribute changes.
but! Inside the React component, it is completely possible to have responsive capabilities, because

What kind of framework is direflow?

In fact, direflow is essentially a web component framework for React component + web component + web component property changes to remount React components .

Therefore, the responsiveness of direflow is actually divided into two parts:
Reactive inside the component (through React's own reactive process), reactive outside the component (the WebComponents property change listens to re-render the component).

If the external properties do not change frequently, there is no problem with performance, because the responsiveness inside the component is completely the responsiveness of React itself.
Attributes If external attributes change frequently, the direflow framework still has some room for optimization in this area.


趁你还年轻
4.1k 声望4.1k 粉丝