• WebComponents框架direflow实现原理


    这个框架支持React方式写WebComponents。

    框架地址:github.com/Silind-Soft…

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

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

    完整构建步骤

    一个完整的direflow web component组件,包含以下步骤。

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

    direflow

    下面再来详细分析一下:

    假设direflow的配置如下:

    import { DireflowComponent } from "direflow-component";
    import  App from "./app";
    
    export default DireflowComponent.create({
      component: App,
      configuration: {
        tagname: "test-component",
        useShadow: true,
      },
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创建一个Web component

    const WebComponent = new WebComponentFactory(
      componentProperties,
      component,
      shadow,
      anonymousSlot,
      plugins,
      callback,
    ).create();
    
    customElements.define(tagName, WebComponent); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过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);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    const componentProperties = {
      ...componentConfig?.properties,
      ...component.properties,
      ...component.defaultProps,
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面这段代码中的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();
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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();
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    创建一个React组件

    对应上面的this.mountReactApp。

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

    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); 
    
    • 1
    • 2

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

    创建一个代理组件

    主要是将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;
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    思考

    为什么要每一次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框架在这块还有一定的优化空间。

  • 相关阅读:
    StringAOP统一问题处理
    DAT:Vision Transformer with Deformable Attention
    Python中Mock和Patch的区别
    阿里互联网一线大厂 Java 岗面试题库(2022 年版)
    UDS入门至精通系列:Service 27
    博文增加商业化内容广告卡片,征求反馈
    使用HTML制作一个端午赛龙舟小游戏
    基于PHP+MySQL学生信息管理系统的开发与设计
    测试开发怎么学?
    Vue3 - Tree Shaking 摇树优化(它是什么?跟 Vue3 有什么关系?)
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125530424