React大家都很熟悉,WebComponents估计也听说过。那么React+WebComponents会碰撞出怎样的火花呢?其实有这么一个支持React写组件的开源框架,最终封装出来的产品就是WebComponents。它就是direflow,一个支持React编写WebComponents的框架。框架地址:https://github.com/Silind-Sof...为什么选择direflow?开源社区中有很多WebComponents框架,比如stencil、lit等,这些框架在社区中活跃度很高,有很多实践实践,但是它们都有一些不适合我们场景的问题:各有各的一套DSL,有一定的学习成本。缺乏基于React的AntD、direflow等基于React技术栈的最佳实践可以完美避免上述问题。React方式编写组件,零学习成本增加基于Webpack封装,AntD等React技术栈可以直接使用落地解析暂时落地WebComponents。目前我们已经落地了多个场景,大大提高了开发效率。很大程度上降低了业务线的接入成本,所以分阶段进行总结。假设有这样一个web组件。原理分析完整的构建步骤一个完整的direflowweb组件包括以下步骤。创建一个web组件标签创建一个React组件,将attributes转化为properties传给React组件(通过Object.defineProperty劫持,通过attributeChangedCallback实时刷新属性)并将这个React应用挂载到web组件的shadowRoot更多详情下面分析:假设direflow配置如下:import{DireflowComponent}from"direflow-component";importAppfrom"./app";exportdefaultDireflowComponent.create({component:App,configuration:{tagname:"test-component",useShadow:true,},});创建一个Web组件constWebComponent=newWebComponentFactory(componentProperties,component,shadow,anonymousSlot,plugins,callback,).create();customElements.define(tagName,WebComponent);通过customElements.define声明一个web组件,tagName为“test-component”,WebComponent是web组件工厂函数的一个实例,集成了渲染react组件的能力。Web组件工厂函数反应性劫持所有属性。publicsubscribeToProperties(){constpropertyMap={}作为PropertyDescriptorMap;Object.keys(this.initialProperties).forEach((key:string)=>{propertyMap[key]={configurable:true,enumerable:true,set:(newValue:unknown)=>{constoldValue=this.properties.hasOwnProperty(key)?this.properties[key]:this.initialProperties[key];this.propertyChangedCallback(key,oldValue,newValue);},};});Object.defineProperties(this,propertyMap);}首先,将属性转换为属性。其次,通过Object.defineProperties劫持属性,触发setter中的propertyChangedCallback函数。constcomponentProperties={...componentConfig?.properties,...component.properties,...component.defaultProps,};当上述代码中的属性发生变化时,重新挂载React组件。(这一般是为了开发环境获取最新的视图)/***当一个属性发生变化时,会调用这个回调函数。*/publicpropertyChangedCallback(name:string,oldValue:unknown,newValue:unknown){这个。属性[名称]=新值;this.mountReactApp();}当属性改变时,重新挂载组件,此时会触发web组件原生的attributeChangedCallback。publicattributeChangedCallback(name:string,oldValue:string,newValue:string){constpropertyName=factory.componentAttributes[name].property;this.properties[propertyName]=getSerialized(newValue);this.mountReactApp();}创建一个React组件,对应上面的this.mountReactApp。{React.createElement(factory.rootComponent,this.reactProps(),anonymousSlot)}EventProvider——创建一个EventContext封装组件,供web组件与外界通信。factory.rootComponent-传入DireflowComponent.create的组件作为根组件,通过React.createElement创建。this.reactProps()-获取序列化属性。为什么要序列化,因为html标签的属性只接受字符串类型。所以需要通过JSON.stringify()进行序列化和传值,而JSON.parse会在工厂函数内部完成。ConvertattributetopropertyanonymousSlot——匿名插槽,插槽。内容可以直接在Web组件标签内分发。将React应用程序挂载到Web组件constroot=createProxyRoot(this,shadowChildren);ReactDOM.render({applicationWithPlugins},this);代理组件使用React组件作为子组件,ReactDOM渲染代理组件。创建代理组件主要是将WebComponentizedReact组件挂载到web组件的shadowRoot上。constcreateProxyComponent=(options:IComponentOptions)=>{constShadowRoot:FC=(props)=>{constshadowedRoot=options.webComponent.shadowRoot||options.webComponent.attachShadow({mode:options.mode});options.shadowChildren.forEach((child)=>{shadowedRoot.appendChild(child);});返回{props.children};};返回ShadowRoot;};获得shadowRoot,如果没有,attachShadow创建一个新的shadowroot。将子节点添加到影子根。返回一个Portal,它将组件安装到影子根并接收子级的高阶函数。想想为什么每次属性变化都需要重新挂载ReactApp?不可以把它当做一个常规的react组件,利用react自身的刷新能力吗?因为direflow的最终产品是一个webcomponent。属性变化,react无法自动感知这种变化,所以需要通过监听属性变化来重新挂载ReactApp。但!React组件内部,完全可以有响应能力,因为direflow是个什么框架?其实direflow本质上是一个React组件+web组件+web组件属性的改变,重新挂载React组件的web组件框架。因此,direflow的响应性实际上分为两部分:组件内部响应性(通过React自身的响应过程)和组件外部响应性(WebComponents属性变化监听器重新渲染组件)。如果外部属性不经常变化,性能上没有问题,因为组件内部的响应性与React自身的响应性完全不同。属性如果外部属性变化频繁,direflow框架在这方面还是有一定优化空间的。