這個框架支援React方式寫WebComponents。
框架地址:https://github.com/Silind-Sof...
假設有這樣一個web component元件。
<test-component name="jack" age="18" />
完整構建步驟
一個完整的direflow web component元件,包含以下步驟。
- 建立一個web component標籤
- 建立一個React元件,將attributes轉化為properties屬性轉化並傳入React元件(通過Object.defineProperty做劫持,通過attributeChangedCallback做attribute實時重新整理)
- 將這個React應用,掛載到web component的shadowRoot
下面再來詳細分析一下:
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的高階函式。
思考
為什麼要每一次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框架在這塊還有一定的優化空間。