常州做網(wǎng)站麥策電商戶外廣告
[React]利用Webcomponent封裝React組件
為什么這么做
我個人認(rèn)為,最重要的點(diǎn)是可以很方便地跨框架掛載和卸載wc元素(至少我在項目里是這么玩的),此外,基于wc的css沙箱以及它的shadowRoot機(jī)制,可以提供一套隔離機(jī)制,保證每個渲染組件的邊界分明。
利用AI總結(jié)羅列了一下都有啥優(yōu)點(diǎn)…
- 封裝性:Web Components提供了一種封裝UI組件的方法,使得組件可以在不同的框架或無框架環(huán)境中重用。
- 可重用性:封裝為Web Components的React組件可以在任何支持Web Components的環(huán)境中使用,不限于React應(yīng)用。
- 封裝的樣式和行為:Web Components允許你封裝組件的HTML結(jié)構(gòu)、樣式和行為,確保樣式和行為不會泄露到父組件或全局作用域。
- 獨(dú)立性:Web Components封裝的組件具有獨(dú)立性,它們擁有自己的DOM樹和作用域,不會影響外部環(huán)境。
- 易于集成:Web Components提供了一種標(biāo)準(zhǔn)化的集成方式,可以更容易地將React組件集成到其他Web應(yīng)用中。
- 更好的性能:Web Components的自定義元素可以在不影響主線程的情況下進(jìn)行升級和渲染,這有助于提高應(yīng)用性能。
- 標(biāo)準(zhǔn)化:Web Components基于W3C標(biāo)準(zhǔn),這意味著它們在不同的瀏覽器和環(huán)境中具有更好的一致性和兼容性。
- 易于維護(hù):由于Web Components封裝的組件具有清晰的接口和封裝性,維護(hù)和更新組件變得更加容易。
- 樣式隔離:Web Components的Shadow DOM技術(shù)可以確保組件的樣式不會受到外部樣式的影響,同時也防止組件內(nèi)部樣式泄露到外部。
- 生命周期管理:Web Components允許你定義組件的生命周期鉤子,如
connectedCallback
、disconnectedCallback
等,這與React組件的生命周期方法類似。 - 跨框架使用:封裝為Web Components的React組件可以被其他前端框架或庫使用,例如Vue、Angular或原生JavaScript。
- 自定義元素:Web Components允許開發(fā)者定義自定義HTML元素,這些元素可以像標(biāo)準(zhǔn)HTML元素一樣使用。
- 易于測試:Web Components的封裝性使得測試組件變得更加簡單,因為你可以獨(dú)立于其他組件來測試它們。
- 更好的封裝和抽象:Web Components提供了一種封裝和抽象UI組件的方式,使得組件的實現(xiàn)細(xì)節(jié)對使用者是透明的。
Webcomponent入門
先來簡單地過一下webcomponent的基礎(chǔ)
官方文檔:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components
示例
下面是一個最簡單的示例,自定義了一種名為”simple-component“的元素,并且它沒有shadowRoot(意味著它并沒有與外界隔離樣式)。
class SimpleComponent extends HTMLElement {constructor() {super();this.innerHTML = `<p>Hello, World!</p>`;}
}customElements.define('simple-component', SimpleComponent);
下面是一個內(nèi)容更豐富一些的示例,有基礎(chǔ)的大概過一眼也知道大概了。
// 1.自定義標(biāo)簽都是用class 的形式去繼承
class myDiv extends HTMLElement {// 監(jiān)聽static get observedAttributes() {return ['option']}constructor() {super()// 這樣我們才能夠去追加元素this.attachShadow({ mode: 'open' })}// 重要:生命周期方法 開始connectedCallback() {console.log('connectedCallback生命周期')this.render({option: this.getAttribute('option'),})// 獲取元素console.log(this.shadowRoot.querySelector('.content'))console.log('this.shadowRoot: ', this.shadowRoot)document.addEventListener('click', e => {// 重要:冒泡的順序,通過這個可以判斷有沒有在鼠標(biāo)內(nèi)部進(jìn)行點(diǎn)擊if (e.composedPath().includes(this)) {console.log('點(diǎn)擊了里面')}})this.shadowRoot.querySelector('.content').addEventListener('click', e => {console.log('e: ', e)// window.dispatchEvent})}// 重要:生命周期方法 重新渲染 .甚至還是第一次進(jìn)行渲染,比connect還快// 會重新渲染 connectCallbackattributeChangedCallback(attr, oldValue, newValue) {if (oldValue) {switch (attr) {case 'option':this.shadowRoot.querySelector('.title').textContent = newValue}}console.log('attributeChangeCallback', attr, oldValue, newValue)}borderAdd() {console.log('borderadd')this.shadowRoot.querySelector('.content').style.border = '3px solid green'}render(data) {let { option } = data// console.log()let nodeTemplate = document.createElement('template')nodeTemplate.innerHTML = `<div class="content" ><div class="title">${option} </div> <slot name="container"></slot></div>`let nodeStyles = document.createElement('style')// shadow dom 的樣式絕對隔離// 重要: :host選擇器可以選中根也就是my-div的樣式。外面的選擇器樣式要高于這個nodeStyles.innerHTML = `:host(.active) .content{margin-top:20px;background:rgba(0,0,0,30%);}:host{display:block}.content{width:100px;height:100px;background:rgba(0,0,0,20%)}::slotted([slot="container"]){display:none}::slotted(.active){display:block}`this.shadowRoot.appendChild(nodeTemplate.content)this.shadowRoot.appendChild(nodeStyles)setTimeout(() => {this.borderAdd()}, 3000)}
}// 名字必須小寫 駝峰必須要轉(zhuǎn)成橫線
customElements.define('my-div', myDiv)
shadowRoot
一個Web組件可以有且僅有一個shadowRoot
。shadowRoot
是與該組件關(guān)聯(lián)的影子DOM的根節(jié)點(diǎn)。當(dāng)使用attachShadow
方法創(chuàng)建影子DOM時,它會返回一個shadowRoot
對象,這個對象是唯一的,并且與創(chuàng)建它的元素關(guān)聯(lián)。
例如:
class MyComponent extends HTMLElement {constructor() {super();this.shadow = this.attachShadow({ mode: "open" });this.shadow.innerHTML = `<p>I am in the shadow DOM!</p>`;}
}customElements.define('my-component', MyComponent);
在這個例子中:
MyComponent
類擴(kuò)展了HTMLElement
,定義了一個Web組件。- 在構(gòu)造函數(shù)中,通過調(diào)用
this.attachShadow({ mode: "open" })
創(chuàng)建了一個shadowRoot
,并將其存儲在this.shadow
變量中。 - 這個
shadowRoot
是唯一的,并且與MyComponent
實例關(guān)聯(lián)。
關(guān)鍵點(diǎn):
- 唯一性:每個Web組件實例只能有一個
shadowRoot
。 - 關(guān)聯(lián)性:
shadowRoot
與創(chuàng)建它的Web組件實例是緊密關(guān)聯(lián)的,不能被其他組件實例訪問。
因此,盡管可以在shadowRoot
內(nèi)創(chuàng)建多個子元素和結(jié)構(gòu),但每個Web組件實例只能有一個shadowRoot
。這有助于保持組件的封裝性和獨(dú)立性。
生命周期
connectedCallback
:- 當(dāng)自定義元素被插入到文檔DOM樹中時調(diào)用此方法。這類似于React中的
componentDidMount
。
- 當(dāng)自定義元素被插入到文檔DOM樹中時調(diào)用此方法。這類似于React中的
disconnectedCallback
:- 當(dāng)自定義元素從DOM樹中移除時調(diào)用此方法。類似于React中的
componentWillUnmount
。
- 當(dāng)自定義元素從DOM樹中移除時調(diào)用此方法。類似于React中的
attributeChangedCallback
:- 當(dāng)自定義元素的屬性被更改時調(diào)用此方法。它接收三個參數(shù):屬性名稱、舊值和新值。這可以用于響應(yīng)屬性的變化,類似于React中的
componentDidUpdate
,但是它是針對屬性而不是狀態(tài)。
- 當(dāng)自定義元素的屬性被更改時調(diào)用此方法。它接收三個參數(shù):屬性名稱、舊值和新值。這可以用于響應(yīng)屬性的變化,類似于React中的
adoptedCallback
:- 當(dāng)自定義元素被移動到新的文檔時調(diào)用此方法。這在Web Components中是特有的,因為自定義元素可以跨文檔使用。
主要屬性
- 觀察者模式(Observed attributes):
- 通過在自定義元素類中定義一個靜態(tài)的
observedAttributes
屬性數(shù)組,可以指定哪些屬性的更改應(yīng)該觸發(fā)attributeChangedCallback
。
- 通過在自定義元素類中定義一個靜態(tài)的
connected
和disconnected
屬性:- 這些屬性可以用于檢查自定義元素是否已經(jīng)連接到文檔的DOM樹中。
shadowRoot
屬性:- 每個自定義元素都有一個
shadowRoot
屬性,它是一個Shadow DOM樹的根??梢栽谶@個屬性上使用生命周期回調(diào)來管理Shadow DOM的創(chuàng)建和更新。
- 每個自定義元素都有一個
constructor
:- 雖然不是Web Components的生命周期回調(diào),但是自定義元素的構(gòu)造函數(shù)是定義元素屬性和方法的地方,并且在元素實例化時調(diào)用。
Lit框架入門
一般知道了上面的基礎(chǔ),就可以寫wc組件了,但實際開發(fā)中,肯定還是需要借助一些已有的開發(fā)框架來輔助開發(fā),而Lit就是目前最成熟且使用量最高的。
原理介紹
Web組件的更新并不是每次都進(jìn)行全量更新。Web組件的更新機(jī)制非常靈活,能夠根據(jù)組件的狀態(tài)和屬性的變化來決定是否需要更新。以下是一些關(guān)鍵點(diǎn):
-
屬性變化觸發(fā)更新:
- Web組件的更新通常是由屬性的變化觸發(fā)的。當(dāng)組件的屬性發(fā)生變化時,瀏覽器會調(diào)用
attributeChangedCallback
方法來處理這些變化。
- Web組件的更新通常是由屬性的變化觸發(fā)的。當(dāng)組件的屬性發(fā)生變化時,瀏覽器會調(diào)用
-
狀態(tài)變化觸發(fā)更新:
- 組件的內(nèi)部狀態(tài)變化也可能導(dǎo)致更新。例如,在LitElement中,當(dāng)使用
@property
裝飾器定義的屬性發(fā)生變化時,會觸發(fā)更新。
- 組件的內(nèi)部狀態(tài)變化也可能導(dǎo)致更新。例如,在LitElement中,當(dāng)使用
-
生命周期方法:
- 組件的生命周期方法,如
connectedCallback
,disconnectedCallback
,adoptedCallback
,firstUpdated
,updated
等,都可以在特定時機(jī)觸發(fā)更新。
- 組件的生命周期方法,如
-
選擇性更新:
- 更新機(jī)制可以是選擇性的。例如,在LitElement中,可以通過使用
requestUpdate
方法來請求更新,而不必每次都進(jìn)行全量更新。
- 更新機(jī)制可以是選擇性的。例如,在LitElement中,可以通過使用
-
虛擬DOM:
- 一些Web組件框架(如LitElement)使用虛擬DOM技術(shù)來優(yōu)化更新過程。虛擬DOM可以比較組件的新舊狀態(tài),并只更新那些實際發(fā)生變化的部分。
-
優(yōu)化性能:
- 為了避免不必要的全量更新,Web組件通常會使用一些優(yōu)化技術(shù),例如節(jié)流(throttle)和防抖(debounce)來減少更新次數(shù)。
-
自定義渲染邏輯:
- 開發(fā)者可以通過自定義渲染邏輯來控制組件的更新過程。例如,可以在
render
方法中手動決定哪些部分需要重新渲染。
- 開發(fā)者可以通過自定義渲染邏輯來控制組件的更新過程。例如,可以在
-
條件渲染:
- 組件可以通過條件渲染來決定是否需要更新某些部分。例如,只有當(dāng)特定條件滿足時才重新渲染某些元素。
示例
以下是一個使用LitElement的示例,展示了如何控制組件的更新:
import { LitElement, html, css, property } from 'lit';class MyComponent extends LitElement {@property({ type: String })message = '';render() {return html`<div><p>${this.message}</p></div>`;}updated(changedProperties) {super.updated(changedProperties);if (changedProperties.has('message')) {console.log('Message updated:', this.message);}}
}customElements.define('my-component', MyComponent);
在這個示例中:
message
屬性使用@property
裝飾器定義,當(dāng)其值發(fā)生變化時,會觸發(fā)組件的更新。render
方法定義了組件的渲染邏輯,只有當(dāng)message
屬性發(fā)生變化時,相關(guān)的部分才會重新渲染。updated
方法在組件更新后被調(diào)用,可以在這里處理更新后的邏輯。
通過這種方式,Web組件可以有效地控制更新過程,避免不必要的全量更新,從而提高性能。
增加的生命周期和內(nèi)置屬性
Lit 相對于傳統(tǒng) Web 組件規(guī)范增加的一些生命周期鉤子和特性:
-
render
方法:- 這是 Lit 的核心特性之一。
render
方法是一個返回組件模板的函數(shù),Lit 會根據(jù)這個方法的內(nèi)容來渲染組件的 UI。
- 這是 Lit 的核心特性之一。
-
update
方法:- 這個方法在組件的屬性或狀態(tài)發(fā)生變化時被調(diào)用。Lit 會調(diào)用這個方法來決定是否需要重新渲染組件。
-
shouldUpdate
方法:- 這個方法允許開發(fā)者自定義更新邏輯,決定是否需要進(jìn)行更新。如果返回
false
,則跳過更新。
- 這個方法允許開發(fā)者自定義更新邏輯,決定是否需要進(jìn)行更新。如果返回
-
willUpdate
方法:- 在組件更新之前被調(diào)用,可以用于執(zhí)行更新前的準(zhǔn)備工作。
-
updated
方法:- 在組件更新之后被調(diào)用,可以用于執(zhí)行更新后的邏輯處理。
-
firstUpdated
方法:- 在組件首次更新后被調(diào)用。這與 Web 組件的
connectedCallback
有些相似,但專門用于處理首次渲染后的邏輯。
- 在組件首次更新后被調(diào)用。這與 Web 組件的
-
connectedCallback
:- 這是 Web 組件規(guī)范中的方法,Lit 也支持。當(dāng)組件被插入到文檔中時調(diào)用。
-
disconnectedCallback
:- 這是 Web 組件規(guī)范中的方法,Lit 也支持。當(dāng)組件從文檔中移除時調(diào)用。
-
attributeChangedCallback
:- 這是 Web 組件規(guī)范中的方法,Lit 也支持。當(dāng)組件的屬性發(fā)生變化時調(diào)用。
-
adoptedCallback
:- 這是 Web 組件規(guī)范中的方法,Lit 也支持。當(dāng)組件被移動到新文檔時調(diào)用。
-
requestUpdate
方法:- 這個方法可以被開發(fā)者調(diào)用,以請求更新組件的屬性。Lit 會安排在下一個微任務(wù)中處理這些更新。
-
updateComplete
Promise:- 一個 Promise,當(dāng)組件的更新完成后會解析。這可以用于在更新完成后執(zhí)行異步操作。
-
樣式管理:
- Lit 提供了
CSSResult
和unsafeCSS
等 API,用于更安全和方便地管理組件的樣式。
- Lit 提供了
-
屬性裝飾器:
- 使用
@property
裝飾器定義的屬性會觸發(fā)更新,并且可以指定屬性的類型和是否同步到 DOM 屬性。
- 使用
-
狀態(tài)管理:
- Lit 通過
state
方法和reactive
裝飾器,提供了一種聲明式的方式來管理組件的狀態(tài)。
- Lit 通過
核心:結(jié)合Lit框架實現(xiàn)React組件封裝
那么基于以上,我們可以很容易地就實現(xiàn)利用Lit框架創(chuàng)造出一個webcomponent容器,然后用來包裹React組件。
Base基礎(chǔ)類
import { LitElement, ReactiveElement, adoptStyles, unsafeCSS, PropertyValues } from 'lit'
import { property } from 'lit/decorators.js'type ThrottleFn = (...args: any[]) => void
type DelayFn = (fn: ThrottleFn) => voidconst throttleWith = <T extends ThrottleFn>(fn: T,delayFn: DelayFn,leading = false
): T => {let lastArgs: Parameters<T>, lastThis: unknown, isWaiting = falseconst throttledFn = (...args: Parameters<T>) => {lastArgs = args// eslint-disable-next-linelastThis = thisif (!isWaiting) {if (leading) {fn.apply(lastThis, lastArgs)}isWaiting = truedelayFn(() => {fn.apply(lastThis, lastArgs)isWaiting = false})}}return throttledFn as T
}export default class Base extends LitElement {private _wcStyle?: string@property({ attribute: 'wc-style' })get wcStyle() {return this._wcStyle}set wcStyle(val: string | undefined) {this._wcStyle = valthis.adoptStyles()}/*** 使事件不能跨越ShadowDOM邊界傳播*/@property({ type: Boolean, attribute: 'prevent-compose' })protected preventCompose = false/*** 使事件不冒泡*/@property({ type: Boolean, attribute: 'prevent-bubbles' })protected preventBubbles = false// 應(yīng)用樣式protected adoptStyles = throttleWith(() => {const apply = () => {if (this.renderRoot instanceof ShadowRoot) {const styles = (this.constructor as typeof ReactiveElement).elementStyles.slice() // 獲取原有樣式this.wcStyle && styles.push(unsafeCSS(this.wcStyle))adoptStyles(this.renderRoot, styles) // 重新應(yīng)用樣式}}this.renderRoot ? apply() : this.updateComplete.then(apply)},(fn: any) => Promise.resolve().then(fn))// 派發(fā)事件emit(eventName: string, detail?: any, options?: CustomEventInit) {let event = new CustomEvent(eventName, {detail,composed: !this.preventCompose,bubbles: !this.preventBubbles,cancelable: false,...options,})this.dispatchEvent(event)return event}// 判斷 slot 是否傳入內(nèi)容hasSlot(name?: string) {if (name && name !== 'default') {return !![...this.childNodes].find(node => node.nodeType === node.ELEMENT_NODE && (node as Element).getAttribute('slot') === name)}return [...this.childNodes].some(node => {if (node.nodeType === node.TEXT_NODE && !!node.textContent?.trim()) {return true}if (node.nodeType === node.ELEMENT_NODE) {const el = node as HTMLElementif (!el.hasAttribute('slot')) {return true}}return false})}// 各個生命周期// 掛載時connectedCallback() {super.connectedCallback()console.log('Custom element added to page.')// 第一次被插入文檔時執(zhí)行,跳過節(jié)點(diǎn)刪除后又重新插入的情形if (!this.hasUpdated) {this.setAttribute('wc-component', '')this.setAttribute('wc-pending', '')}}// 卸載時disconnectedCallback() {super.disconnectedCallback()console.log('Custom element removed from page.')}// 移動到另一個文檔的時候adoptedCallback() {console.log('Custom element moved.')}// 元素的屬性被添加、刪除或修改時調(diào)用attributeChangedCallback(name: string, oldValue: any, newValue: any) {super.attributeChangedCallback(name, oldValue, newValue)console.log(`Attribute ${name} has changed.`)}// 或使用靜態(tài)屬性代替get方法static get observedAttributes() {// 指定要監(jiān)聽的元素的屬性數(shù)組// 對應(yīng)的attr改變后,會觸發(fā)attributeChangedCallback// return ['name', 'date']return []}// 是否應(yīng)該更新protected shouldUpdate(_changedProperties: PropertyValues): boolean {return true}// 即將更新protected willUpdate(_changedProperties: PropertyValues): void {super.willUpdate(_changedProperties)console.log('willUpdate')}// 首次更新元素時調(diào)用。實現(xiàn)在更新后對元素執(zhí)行一次性工作protected firstUpdated(changedProperties: PropertyValues) {super.firstUpdated(changedProperties)console.log('this.hasUpdated: ', this.hasUpdated)// this.requestUpdate()// 兩幀數(shù)后執(zhí)行requestAnimationFrame(() => {requestAnimationFrame(() => {this.removeAttribute('wc-pending')})})}protected updated(_changedProperties: PropertyValues): void {super.updated(_changedProperties)this.updateComplete.then((res) => {console.log('updateComplete', res)})}
}
withProperties封裝
import type { LitElement, PropertyValues } from 'lit'type Constructor<T> = new (...args: any[]) => Texport default <T extends Constructor<LitElement>>(superClass: T) => {class WithPropertiesElement extends superClass {props: Record<string, any> = {}willUpdate(changedProperties: PropertyValues) {const obj = [...changedProperties.entries()].reduce<any>((obj, [key]) => ((obj[key] = (this as any)[key]), obj),{})this.props = { ...this.props, ...obj }super.willUpdate(changedProperties)}}return WithPropertiesElement as Constructor<{props: Record<string, any>}> & T
}
這段代碼定義了一個高階組件(Higher-Order Component,HOC),用于增強(qiáng) LitElement 組件的功能。具體來說,它的作用是:
-
創(chuàng)建一個帶有額外屬性管理功能的組件類:
- 通過擴(kuò)展傳入的基類(比如
LitElement
或其子類),添加一個props
屬性來存儲組件的屬性值。
- 通過擴(kuò)展傳入的基類(比如
-
在組件更新前處理屬性變化:
- 重寫
willUpdate
生命周期方法,這個方法在組件的屬性發(fā)生變化并且組件即將更新之前被調(diào)用。
- 重寫
-
收集并存儲屬性變化:
- 使用
changedProperties
對象(一個 Map 類型的對象,包含屬性名和屬性變化的信息)來收集屬性的變化。 - 將變化的屬性存儲到
this.props
對象中,這樣可以通過props
屬性訪問組件的所有屬性值。
- 使用
-
保持基類的
willUpdate
方法的調(diào)用:- 調(diào)用
super.willUpdate(changedProperties)
以確?;惖?willUpdate
方法也能正常執(zhí)行。
- 調(diào)用
代碼詳解
- 定義了一個默認(rèn)導(dǎo)出的函數(shù),它接受一個構(gòu)造函數(shù)
superClass
(應(yīng)該是LitElement
或其子類的構(gòu)造函數(shù))。 - 創(chuàng)建一個新類
WithPropertiesElement
,繼承自superClass
。 - 在
WithPropertiesElement
類中定義了一個props
屬性,用于存儲屬性值。 - 重寫
willUpdate
方法,在組件更新前處理屬性變化,并將變化的屬性存儲到this.props
中。 - 返回
WithPropertiesElement
類,并通過類型斷言確保它具有額外的props
屬性。
使用示例
假設(shè)你有一個基礎(chǔ)的 LitElement 組件:
import { LitElement, html } from 'lit';class MyElement extends LitElement {count = 0;render() {return html`<p>Count: ${this.count}</p>`;}
}customElements.define('my-element', MyElement);
你可以使用這個高階組件來增強(qiáng)它:
import { WithPropertiesElement } from './WithPropertiesElement';
import { LitElement, html } from 'lit';const EnhancedElement = WithPropertiesElement(MyElement);customElements.define('enhanced-element', EnhancedElement);const element = new EnhancedElement();
document.body.appendChild(element);console.log(element.props); // { count: 0 }
在這個示例中,EnhancedElement
繼承自 MyElement
并添加了屬性管理功能。可以通過 element.props
訪問組件的所有屬性值。
這種模式在需要在組件中統(tǒng)一管理屬性或在組件更新前進(jìn)行額外處理時非常有用。
存放React組件的webcomponent基類
重頭戲來了
import { ChildPart, html, PropertyValues } from 'lit'
import { query } from 'lit/decorators.js'
import { Fragment, createElement as h } from 'react'
import ReactDOM from 'react-dom'
import withProperties from '../mixin/withProperties'
import LightBase from './Base'type H = typeof hconst Root: React.FC<any> = props => {return h(Fragment, {...props,})
}const omit = (obj: Record<string, any>, filter: string[] = []) =>Object.fromEntries(Object.entries(obj).filter(([key]) => !filter.includes(key)))// React組件基類
export default class extends withProperties(LightBase) {// 子類要重寫這個方法來渲染自己的組件protected renderReact(h: H): React.ReactNode {return null}protected customContainer(): Element | undefined {return this.$reactRoot}protected getReactProps(props: Record<string, any>) {return omit(props, ['preventCompose', 'preventBubbles', 'localeMessages'])}protected extraStyle = ''@query('.react-root')$reactRoot?: HTMLElementupdated(changed: PropertyValues) {super.updated(changed)this.doRender()}connectedCallback() {super.connectedCallback()// 節(jié)點(diǎn)刪除后重新插入的情形if (this.hasUpdated) {this.doRender()}}disconnectedCallback() {super.disconnectedCallback()this.doUnmount()}private container?: Elementprivate doRender() {const container = this.customContainer()if (!container) {this.doUnmount() // 卸載前一次渲染的內(nèi)容} else {this.container = containerReactDOM.render(h(Root, {}, this.renderReact(h)), container, () => {// hack for error: https://github.com/lit/lit/blob/f8ee010bc515e4bb319e98408d38ef3d971cc08b/packages/lit-html/src/lit-html.ts#L1122// 在React中使用此組件且非首次更新時會報錯,因為lit默認(rèn)會在組件下創(chuàng)建一個注釋節(jié)點(diǎn),更新時會對這個節(jié)點(diǎn)進(jìn)行操作,而React渲染時把這個注釋節(jié)點(diǎn)干掉了,這里要把他加回去const childPart = (this as any).__childPart as ChildPart | undefinedchildPart?.startNode && this.appendChild(childPart.startNode)})}}private doUnmount() {if (this.container) {ReactDOM.unmountComponentAtNode(this.container)}}render() {return html` <div class="react-root"></div> `}
}
使用Demo
import { unsafeCSS } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import ReactBase from './ReactBase'// 自己的React組件
import Component from './Component'import style from './index.less?inline'@customElement('my-diy-react-wc')
export default class DataReport extends ReactBase {static get styles() {return unsafeCSS([style])}/*** 自定義屬性*/@property()language: string = 'zh-CN'// ReactBase中用來渲染React,不要刪除renderReact() {return <Component language={this.language} />}
}
參考文章
https://juejin.cn/post/7296850940404580364?searchId=2024071620331848BC966F0D2051B9C533#heading-9
lit官網(wǎng):https://lit.dev/docs/components/styles/
webcomponent文檔:https://developer.mozilla.org/en-US/docs/Web/API/Web_components