关于前端:WebComponents框架direflow实现原理

27次阅读

共计 4141 个字符,预计需要花费 11 分钟才能阅读完成。

这个框架反对 React 形式写 WebComponents。
框架地址:https://github.com/Silind-Sof…

假如有这样一个 web component 组件。

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

direflow 的配置如下:

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

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

创立一个 Web component

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

customElements.define(tagName, WebComponent);

通过 customElements.define 申明一个 web component,tagName 为 ”test-component”,WebComponent 为集成了渲染 react 组件能力的的 web components 工厂函数实例。

web components 工厂函数

响应式

劫持所有属性。

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);
}

首先,将 attributes 转化为 properties。
其次,通过 Object.defineProperties 劫持 properties,在 setter 中,触发 propertyChangedCallback 函数。

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

下面这段代码中的 property 变动时,从新挂载 React 组件。(这里个别是为了开发环境下,获取最新的视图)

/**
 * When a property is changed, this callback function is called.
 */
public propertyChangedCallback(name: string, oldValue: unknown, newValue: unknown) {if (!this.hasConnected) {return;}

  if (oldValue === newValue) {return;}

  this.properties[name] = newValue;
  this.mountReactApp();}

attribute 变动时,从新挂载组件,此时触发的是 web components 原生的 attributeChangedCallback。

public attributeChangedCallback(name: string, oldValue: string, newValue: string) {if (!this.hasConnected) {return;}

  if (oldValue === newValue) {return;}

  if (!factory.componentAttributes.hasOwnProperty(name)) {return;}

  const propertyName = factory.componentAttributes[name].property;
  this.properties[propertyName] = getSerialized(newValue);
  this.mountReactApp();}

创立一个 React 组件

对应下面的 this.mountReactApp。

<EventProvider>
  {React.createElement(factory.rootComponent, this.reactProps(), anonymousSlot)}
<EventProvider>

EventProvider- 创立了一个 Event Context 包裹组件,用于 web components 组件与内部通信。
factory.rootComponent- 将 DireflowComponent.create 的 component 传入,作为根组件,并且通过 React.createElement 去创立。
this.reactProps()- 取得序列化后的属性。为什么要序列化,因为 html 标签的 attribute,只接管 string 类型。因而须要通过 JSON.stringify()序列化传值,工厂函数外部会做 JSON.parse。将 attribute 转化为 property
anonymousSlot- 匿名 slot,插槽。能够间接将内容散发在 web component 标签外部。

挂载 React 利用到 web component

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

代理组件将 React 组件作为 children,ReactDOM 渲染这个代理组件。

web component 挂载到 DOM 时,挂载 React App

public connectedCallback() {this.mountReactApp({ initial: true});
  this.hasConnected = true;
  factory.connectCallback?.(this);
}

创立一个代理组件

次要是将 Web Component 化后的 React 组件,挂载到 web component 的 shadowRoot。

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

获取到 shadowRoot,没有的话 attachShadow 新建一个 shadow root。
将子结点增加到 shadow root。
返回一个挂载组件到 shadow root 的 Portal,接管 children 的高阶函数。

残缺构建步骤

一个残缺的 direflow web component 组件,蕴含以下步骤。

  1. 创立一个 web component 标签
  2. 创立一个 React 组件,将 attributes 转化为 properties 属性转化并传入 React 组件(通过 Object.defineProperty 做劫持,通过 attributeChangedCallback 做 attribute 实时刷新)
  3. 将这个 React 利用,挂载到 web component 的 shadowRoot

<img width=”592″ alt=”direflow” src=”https://user-images.githubusercontent.com/19262750/165549797-8cec26b6-2e30-4b67-90db-744b8a1a1331.png”>

思考

为什么要每一次 attribute 变动都要从新挂载 React App?不能把它看做一个惯例的 react 组件吗,应用 react 本身的刷新能力?

因为 direflow 的最终产物,是一个 web component 组件。
attribute 变动,react 是无奈主动感知到这个变动的,因而须要通过监听 attribute 变动去从新挂载 React App。
然而!React 组件外部,是齐全能够领有响应式能力的,因为

direflow 是一个什么框架?

其实,direflow 实质上,是一个 React 组件 + web component +web component 属性变动从新挂载 React 组件 的 web component 框架。

所以,direflow 的响应式其实分为 2 块:
组件外部响应式(通过 React 本身响应式流程),组件内部响应式(WebComponents 属性变动监听重渲染组件)。

如果内部属性不会常常变动的话,性能这块没有问题,因为组件外部的响应式齐全是走了 React 本身的响应式。
属性内部属性如果会常常变动的话,direflow 框架在这块还有肯定的优化空间。

正文完
 0