首頁 >web前端 >css教學 >構建可互操作的網絡組件

構建可互操作的網絡組件

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原創
2025-03-13 10:05:10475瀏覽

構建可互操作的網絡組件

我們當中那些曾經是Web開發人員超過幾年的人使用了多個JavaScript框架編寫了代碼。有了所有的選擇 - 反應,苗條,vue,ancular,lood-幾乎是不可避免的。在跨框架工作時,我們必須處理的更令人沮喪的事情之一是重新創建所有這些低級UI組件:按鈕,表,下拉次數等。特別令人沮喪的是,我們通常會在一個框架中定義它們,說出反應,但是如果我們想在友好中構建某些東西,則需要重寫它們。或Vue。或固體。等等。

如果我們可以以框架 - 敏捷的方式定義這些低級UI組件,然後在框架之間重新使用它們,這會更好嗎?當然會的!我們可以; Web組件是一種方式。這篇文章將向您展示如何。

截至目前,網絡組件的SSR故事有點缺乏。聲明性的影子DOM(DSD)是服務器端渲染的方式,但是,在撰寫本文時,它與您喜歡的應用程序框架(例如Next,Remix或Sveltekit)並未集成在一起。如果這是您的要求,請務必檢查DSD的最新狀態。但是否則,如果SSR不是您使用的東西,請繼續閱讀。

首先,一些上下文

Web組件本質上是您定義自己的HTML元素,例如或其他任何內容。它們在CSS-Tricks(包括Caleb Williams的廣泛系列和John Rhea的廣泛系列)中覆蓋了它們,但我們將簡要介紹這一過程。從本質上講,您定義一個JavaScript類,從HTMLELLENT繼承,然後定義Web組件具有的任何屬性,屬性和样式,當然,它最終將向您的用戶呈現標記。

能夠定義與任何特定組件不綁定的自定義HTML元素令人興奮。但是這種自由也是一個限制。獨立於任何JavaScript框架而存在意味著您無法真正與那些JavaScript框架進行交互。想想一個React組件,該組件獲取一些數據,然後呈現其他一些反應組件,並沿數據傳遞。這實際上不能用作Web組件,因為Web組件不知道如何渲染React組件。

Web組件特別像葉片成分一樣出色。葉片成分是在組件樹中渲染的最後一件事。這些是接收一些道具並渲染一些UI的組件。這些不是坐在組件樹中間的組件,而是沿著環境的數據傳遞數據等 - 不管哪個JavaScript框架都在為應用程序的其餘部分供電。

我們正在構建的Web組件

與其構建一些無聊(和常見),例如按鈕,讓我們構建一些不同的東西。在我的上一篇文章中,我們研究了使用模糊圖像預覽以防止內容回流,並在加載圖像時為用戶提供體面的UI。我們查看了base64編碼圖像的模糊,降級版本的編碼,並在加載真實圖像時在UI中顯示了這一點。我們還考慮使用稱為Blurhash的工俱生成令人難以置信的緊湊,模糊預覽。

該帖子向您展示瞭如何生成這些預覽並將其在React項目中使用。這篇文章將向您展示如何使用Web組件中的這些預覽,以便可以通過任何JavaScript框架使用它們。

但是我們需要在跑步之前步行,因此我們首先要瀏覽一些微不足道和愚蠢的東西,以確切了解Web組件的工作原理。

這篇文章中的所有內容都將在沒有任何工具的情況下構建Vanilla Web組件。這意味著代碼將具有一些樣板,但應相對易於遵循。諸如LIT或模板之類的工具設計用於構建Web組件,可用於刪除大部分樣板。我敦促您檢查一下!但是對於這篇文章,我更喜歡更多的樣板,以換取不必介紹和教授其他依賴性。

一個簡單的計數器組件

讓我們構建JavaScript組件的經典“ Hello World”:櫃檯。我們將渲染一個值,並為該值增加一個按鈕。簡單而無聊,但它可以讓我們查看最簡單的Web組件。

為了構建Web組件,第一步是製作一個JavaScript類,該類從htmlelement繼承:

類計數器擴展htmlelement {}

最後一步是註冊Web組件,但前提是我們尚未註冊它:

 if(!customElements.get(“ counter-wc”)){
  customElements.define(“ counter-wc”,計數器);
}

而且,當然,將其渲染:

 <counter-wc> </counter-wc>

兩者之間的所有內容都是使Web組件可以做我們想做的任何事情。一種常見的生命週期方法是ConnectedCallback,當將我們的Web組件添加到DOM中時,該方法會觸發。我們可以使用該方法渲染我們想要的任何內容。請記住,這是一個從HTMLElement繼承的JS類,這意味著我們的此值是Web組件元素本身,使用了您已經知道和喜歡的所有正常DOM操縱方法。

最簡單,我們可以做到這一點:

類計數器擴展htmlelement {
  connectedCallback(){
    this.innerhtml =“ <div style="'顏色:綠色'">嘿</div>”;
  }
}

if(!customElements.get(“ counter-wc”)){
  customElements.define(“ counter-wc”,計數器);
}

…這會很好。

添加真實內容

讓我們添加一些有用的交互式內容。我們需要一個來保持當前數值和

 constructor(){
  極好的();
  const容器= document.createelement('div');

  this.valspan = document.createelement('span');

  const regrement = document.createelement('button');
  rezement.InnerText ='regrement';
  rezement.AddeventListener('click',()=> {
    這個。#value = this。#currentValue 1;
  });

  container.appendchild(this.valspan);
  container.AppendChild(document.createelement('br'));
  Container.AppendChild(增量);

  this.container =容器;
}

connectedCallback(){
  this.appendchild(this.container);
  this.update();
}

如果您真的被手動DOM創建所困擾,請記住,您可以設置InnerHTML,甚至可以將模板元素作為Web組件類的靜態屬性創建,請克隆它,並插入新的Web組件實例中的內容。我可能沒有考慮其他選項,或者您始終可以使用LIT或SCENN等WEB組件框架。但是對於這篇文章,我們將繼續保持簡單。

繼續前進,我們需要一個可設置的JavaScript類屬性,名為Value

 #CurrentValue = 0;

設置#value(val){
  這個。#currentValue = val;
  this.update();
}

它只是設置器的標準類屬性,以及第二個屬性以保持值。一個有趣的轉折是,我將私有JavaScript類屬性語法用於這些值。這意味著在我們的Web組件之外沒有人可以觸摸這些值。這是所有現代瀏覽器中都支持的標準JavaScript,因此不要害怕使用它。

或者,如果您願意,請隨時稱其為_value。最後,我們的更新方法:

更新() {
  this.valspan.innertext = this。#currentValue;
}

有用!

顯然,這不是您要大規模維護的代碼。如果您想仔細觀察,這是一個完整的工作示例。就像我說的那樣,諸如LIT和模板之類的工具旨在使其更簡單。

添加更多功能

這篇文章不是深入研究Web組件。我們不會涵蓋所有的API和生命週期;我們甚至不會覆蓋陰影根或插槽。這些主題有無窮無盡的內容。我的目標是提供足夠不錯的介紹,以引發一些興趣,並提供一些有用的指導,以實現與您已經知道和喜歡的流行JavaScript框架實際使用Web組件。

為此,讓我們稍微增強我們的計數器Web組件。讓我們接受顏色屬性,以控制顯示的值的顏色。而且,我們還可以接受增量屬性,因此該Web組件的消費者一次可以將其增加2、3、4。為了驅動這些狀態變化,讓我們在Svelte Sandbox中使用我們的新計數器 - 我們會稍微做出反應。

我們將從與以前相同的Web組件開始,然後添加顏色屬性。為了配置我們的Web組件以接受並響應屬性,我們添加了一個靜態觀察到的屬性,該屬性返回我們的Web組件聆聽的屬性。

靜態觀察結果= [“顏色”];

有了將其添加,我們可以添加一個屬性callback lifecycle方法,該方法將在設置或更新中列出的任何屬性時運行。

 attributechangedCallback(名稱,oldValue,newValue){
  如果(name ===“顏色”){
    this.update();
  }
}

現在,我們更新我們的更新方法以實際使用它:

更新() {
  this.valspan.innertext = this._currentValue;
  this.valspan.style.color = this.getAttribute(“ color”)|| “黑色的”;
}

最後,讓我們添加我們的增量屬性:

增量= 1;

簡單而謙虛。

在Svelte中使用計數器組件

讓我們使用剛剛做的東西。我們將進入我們的Svelte應用程序組件,並添加類似的內容:

 <script>
  令顏色=“紅色”;
</script>


  主要的 {
    文字平衡:中心;
  }


<ain>
  
    紅色
    綠色
    藍色
  

  <counter-wc color="{color}"> </counter-wc>
</ain>

而且有效!我們的計數器渲染,增量和下拉列表更新顏色。如您所見,我們將顏色屬性呈現在我們的Svelte模板中,當值更改時,Svelte會在我們的基礎Web組件實例上處理調用SetAttribute的腿部工作。這裡沒有什麼特別的:對於任何HTML元素的屬性,這是同一件事。

通過增量道具,事情變得有些有趣。這不是我們Web組件上的屬性;這是Web組件類的道具。這意味著需要在Web組件的實例上設置它。忍受我,因為事情會稍微簡化。

首先,我們將在我們的Svelte組件中添加一些變量:

讓增量= 1;
讓Wcinstance;

我們的計數器組件的強大雄厚將使您增加1個,或以2:2:

  regrement = 1}>增量1 
 regrement = 2}>增量2 

但是,從理論上講,我們需要獲得我們的Web組件的實際實例。每當我們與React添加REF時,這都是同一件事。使用Svelte,這是一個簡單的綁定:此指令:

 <counter-wc bind color="{color}"> </counter-wc>

現在,在我們的Svelte模板中,我們聆聽對組件的增量變量的更改,並設置基礎Web組件屬性。

 $:{
  如果(wcinstance){
    wcinstance.increment =增量;
  }
}

您可以在此現場演示中對其進行測試。

顯然,我們不想為我們需要管理的每個Web組件或道具執行此操作。如果我們只能像平時對組件道具那樣在網絡組件上設置增量,並且讓它僅僅工作,那就不好了嗎?換句話說,如果我們可以刪除WCinstance的所有用法並使用此更簡單的代碼:

 <counter-wc rezement="{rezement}" color="{color}"> </counter-wc>

事實證明我們可以。該代碼有效; Svelte為我們處理了所有的腿部工作。在此演示中檢查一下。這是幾乎所有JavaScript框架的標準行為。

那麼,為什麼我向您展示設置Web組件的道具的手動方法呢?兩個原因:了解這些事情的工作原理很有用,片刻之前,我說這適用於所有JavaScript框架。但是,有一個框架令人瘋狂,不像我們剛剛看到的那樣支持Web組件道具設置。

反應是另一種野獸

反應。地球上最受歡迎的JavaScript框架不支持與Web組件的基本互動。這是一個眾所周知的問題,反應是獨一無二的。有趣的是,這實際上是在React的實驗分支中固定的,但是由於某種原因沒有合併到第18版。也就是說,我們仍然可以跟踪它的進度。您可以通過現場演示自己嘗試。

當然,解決方案是使用參考,抓住Web組件實例,並在該值更改時手動設置增量。看起來像這樣:

導入React,{usestate,useref,useffect}來自'react';
導入'./counter-wc';

導出默認函數app(){
  const [增量,setIncrement] = usestate(1);
  const [color,setColor] = usestate('red');
  const wcref = useref(null);

  useeffect(()=> {
    wcref.current.increment =增量;
  },[增量]);

  返回 (
    <div>
      <div classname="“增量">
        <button onclick="{()="> setIncrement(1)}>通過1 </button>增量
         setIncrement(2)}>通過2 增量
      </div>

      <select value="{color}" onchange="{(e)="> setColor(e.target.value)}>
        紅色
        綠色
        藍色
      </select>

      <counter-wc ref="{wcref}" engrement="{rezement}" color="{color}"> </counter-wc>
    </div>
  );
}
現場演示

正如我們討論的那樣,對每個Web組件屬性手動編碼根本是根本不可擴展的。但是,一切都沒有丟失,因為我們有幾個選擇。

選項1:到處使用屬性

我們有屬性。如果您單擊上面的React演示,則增量道具無法正常工作,但是顏色正確更改。我們不能用屬性編碼所有內容嗎?可悲的是,不。屬性值只能是字符串。這在這裡已經足夠好了,我們將能夠通過這種方法有所了解。諸如增量之類的數字可以從字符串轉換。我們甚至可以json stringify/解析對象。但是最終,我們需要將函數傳遞到Web組件中,到那時我們將無法選擇。

選項2:包裝

有一個古老的說法,您可以通過增加一定程度的間接來解決計算機科學中的任何問題(除了間接程度太多的問題外)。設置這些道具的代碼非常可預測且簡單。如果我們將其隱藏在圖書館中怎麼辦? LIT背後的聰明人有一個解決方案。此庫將其提供給Web組件後,為您創建一個新的React組件,並列出所需的屬性。雖然聰明,但我不喜歡這種方法。

我不喜歡將Web組件一對一的映射到手動創建的React組件,而是只有一個React組件,我們將Web組件標籤名稱傳遞給(在我們的情況下為Counter-WC),以及所有屬性和屬性,以及所有屬性 - 對於此組件,為了使我們的Web組件呈現我們的Web組件,然後添加ref,然後添加probim and a probim and a probimute and a probimute and a probim utibute and a probimutibutibute and a probim utibute utibute。在我看來,這是理想的解決方案。我不知道這樣做的庫,但是創建應該很簡單。讓我們試一試!

這是我們要尋找的用法

 <wcwrapper wctag="“" counter-wc engrement="{rezement}" color="{color}"></wcwrapper>

WCTAG是Web組件標籤名稱;其餘的是我們要通過的屬性和屬性。

這是我的實施方式:

導入React,{createElement,useref,useLayouteffect,emo}來自'react';

const _wcwrapper =(props)=> {
  const {wctag,兒童,... restprops} = props;
  const wcref = useref(null);

  uselayouteffect(()=> {
    const wc = wcref.current;

    for(const [鍵,值]的object..entries(restprops)){
      如果(wc中的鍵){
        如果(wc [key]!== value){
          wc [key] = value;
        }
      } 別的 {
        if(wc.getAttribute(key)!== value){
          wc.setAttribute(鍵,值);
        }
      }
    }
  });

  返回createElement(wctag,{ref:wcref});
};

導出const wcwrapper = memo(_wcwrapper);

最有趣的行是:

返回createElement(wctag,{ref:wcref});

這就是我們在用動態名稱中創建react元素的方式。實際上,這就是通常將JSX轉移到的反應。我們所有的DIV都轉換為CreateElement(“ DIV”)調用。通常,我們不需要直接調用此API,但是當我們需要時它在那裡。

除此之外,我們還希望在我們傳遞給組件的每個道具中運行佈局效果並循環。我們循環遍歷所有這些,並檢查它是否是檢查Web組件實例對象及其原型鏈的屬性,該鏈接將捕獲所有在類原型上浮起的getters/setter。如果不存在此類屬性,則假定它是一個屬性。無論哪種情況,我們僅在值實際更改時將其設置。

如果您想知道為什麼我們使用USELAYOUTEFFECT而不是使用效果,那是因為我們想在渲染內容之前立即運行這些更新。另外,請注意,我們對USELAYOUTEFFECT沒有依賴性數組;這意味著我們希望在每個渲染上運行此更新。這可能是有風險的,因為React傾向於重新渲染很多。我通過將整個東西包裹在react.memo中來改善這一點。這本質上是現代版本的react.purecomponent,這意味著該組件只有在任何實際道具都更改時才會重新渲染 - 它可以通過簡單的平等檢查來檢查這種情況是否發生。

這裡唯一的風險是,如果您通過一個對象支柱,即您直接在不重新分配的情況下進行突變,那麼您將不會看到更新。但這是高度灰心的,尤其是在React社區中,因此我不必擔心。

在繼續之前,我想喊一件最後一件事。您可能對使用方式不滿意。同樣,此組件是這樣使用的:

 <wcwrapper wctag="“" counter-wc engrement="{rezement}" color="{color}"></wcwrapper>

具體來說,您可能不喜歡將Web組件標籤名稱傳遞到組件上,而是上面的 @lit-labs/react軟件包,它為每個Web組件創建一個新的單獨的React組件。這是完全公平的,我鼓勵您使用最適合的一切。但是對我來說,這種方法的優勢是易於刪除。如果通過某些奇蹟React將適當的Web組件從其實驗分支合併到明天的主要分支,則可以從中更改上述代碼:

 <wcwrapper wctag="“" counter-wc engrement="{rezement}" color="{color}"></wcwrapper>

…對此:

 <counter-wc ref="{wcref}</pre"><p>您甚至可以編寫一個codemod來在任何地方執行此操作,然後完全刪除<wcwrapper>。實際上,刮擦:全局搜索和用正則替換可能會起作用。</wcwrapper></p>
<h3>實施</h3>
<p>我知道,似乎花了一段時間到達這裡。如果您還記得,我們最初的目標是將您在上一篇文章中查看的圖像預覽代碼中獲取,並將其移至Web組件,以便在任何JavaScript框架中使用它。 React缺乏適當的Interop,為混合物增加了很多細節。但是,既然我們對如何創建Web組件有一個不錯的處理,並使用它,實現幾乎將是反氣候的。</p>
<p>我將在此處放置整個Web組件,並召集一些有趣的位。如果您想在行動中看到它,這是一個工作的演示。它將在我最喜歡的三種最喜歡的編程語言上的三本最喜歡的書之間切換。每本書的URL每次都會是唯一的,因此您可以看到預覽,儘管您可能想在DevTools網絡選項卡中插入事物,以真正看到事情發生。</p>
<details><summary>查看整個代碼</summary><pre rel="View full code" data-line="">類書房擴展了htmlelement {
  靜態觀察到= ['url'];

  attributechangedCallback(名稱,oldValue,newValue){
    如果(name ==='url'){
      this.CreateMainImage(newValue);
    }
  }

  設置預覽(val){
    this.previewel = this.createpreview(val);
    this.render();
  }

  CreatePreview(val){
    if(typeof val ==='string'){
      返回base64preview(val);
    } 別的 {
      返回blurhashpreview(val);
    }
  }

  createMainImage(url){
    this.loaded = false;
    const img = document.createelement('img');
    img.alt ='書籍封面';
    img.addeventlistener('load',()=&gt; {
      如果(img === this.imageel){
        this.loaded = true;
        this.render();
      }
    });
    img.src = url;
    this.imageel = img;
  }

  connectedCallback(){
    this.render();
  }

  使成為() {
    const elementMaybe = this.poaded? this.imageel:this.previewel;
    SyncsingleChild(this,elementMaybe);
  }
}

首先,我們註冊我們感興趣的屬性並在變化時做出反應:

靜態觀察到= ['url'];

attributechangedCallback(名稱,oldValue,newValue){
  如果(name ==='url'){
    this.CreateMainImage(newValue);
  }
}

這導致創建我們的圖像組件,僅加載時才會顯示:

 createMainImage(url){
  this.loaded = false;
  const img = document.createelement('img');
  img.alt ='書籍封面';
  img.addeventlistener('load',()=> {
    如果(img === this.imageel){
      this.loaded = true;
      this.render();
    }
  });
  img.src = url;
  this.imageel = img;
}

接下來,我們將擁有我們的預覽屬性,它可以是我們的base64預覽字符串,也可以是我們的Blurhash數據包:

設置預覽(val){
  this.previewel = this.createpreview(val);
  this.render();
}

CreatePreview(val){
  if(typeof val ==='string'){
    返回base64preview(val);
  } 別的 {
    返回blurhashpreview(val);
  }
}

此辯護給我們需要的任何輔助功能:

函數base64preview(val){
  const img = document.createelement('img');
  img.src = val;
  返回IMG;
}

函數BlurhashPreview(Preview){
  const canvasel = document.createelement('canvas');
  const {w:width,h:height} = preview;

  canvasel.width = width;
  canvasel.height =高度;

  const pixels = decode(preview.blurhash,寬度,高度);
  const ctx = canvasel.getContext('2d');
  const imagedata = ctx.createimagedata(寬度,高度);
  imagedata.data.set(像素);
  ctx.putimagedata(Imagedata,0,0);

  返回畫布;
}

最後,我們的渲染方法:

 connectedCallback(){
  this.render();
}

使成為() {
  const elementMaybe = this.poaded? this.imageel:this.previewel;
  SyncsingleChild(this,elementMaybe);
}

還有一些幫助所有東西將所有東西綁在一起的方法:

導出函數SyncsingleChild(容器,兒童){
  const CurrentChild = container.firstelementChild;
  if(currentchild!== child){
    ClearContainer(容器);
    if(child){
      container.AppendChild(兒童);
    }
  }
}

導出函數clearContainer(el){
  讓孩子

  while(((孩子= el.firstelementChild)){
    El.Removechild(兒童);
  }
}

它比我們在框架中構建它所需的樣板要多一點,但是好處是,我們可以在我們想要的任何框架中重新使用它 - 儘管正如我們所討論的那樣,React現在需要一個包裝器。

什物

我已經提到了Lit的React包裝器。但是,如果您發現自己使用模具,它實際上支持僅用於React的單獨輸出管道。微軟的好人還創建了類似於LIT的包裝器的東西,該包裝器附加在快速Web組件庫上。

正如我提到的,所有未命名React的框架都將處理為您設置Web組件屬性。請注意,有些具有一些特殊的語法風味。例如,使用solid.js,始終假定值是一個屬性,您可以用attr前綴覆蓋,例如

總結

Web組件是Web開發環境中有趣的,通常是未充分利用的部分。它們可以通過管理UI或“ Leaf”組件來幫助您減少您對任何單個JavaScript框架的依賴。在將它們創建為網絡組件(而不是Svelte或React組件)的同時,並不會那麼符合人體工程學,但上升過程是它們將被廣泛重複使用。

以上是構建可互操作的網絡組件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn