<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Web組件構建庫-Lit

          共 12528字,需瀏覽 26分鐘

           ·

          2022-06-11 13:25

          ????


          認識Lit

          抽象與封裝

          在《你真的了解Web Component嗎[1]》的分享中,我們在介紹web組件前,從理解框架和職責范圍的出發(fā)點與角度,探究了框架存在和發(fā)展的意義及目標,了解到了框架可以使快速開發(fā)和基礎性能之間達成平衡,進而讓開發(fā)者的開發(fā)體驗得到較大的提升。

          而一個框架的組成,離不開優(yōu)秀的設計思想,和對這些設計思想的最終實現(xiàn)。實現(xiàn)的整個過程,其實就是一個抽象與封裝的過程。但是這個過程并非是框架獨屬的,我們可以回憶一下,日常的開發(fā)中,可以對某些頻繁、重復使用的邏輯進行函數(shù)式封裝;可以將某個到處使用的模版進行組件式封裝;甚至我們會引用一些高質(zhì)量的庫去支持開發(fā),而這些庫,也是一個抽象與封裝的結果。既然抽象與封裝應用如此廣泛,那么web component的創(chuàng)建與使用是不是也可以形成一個抽象與封裝的產(chǎn)物呢?

          Lit的介紹

          Lit是一個輕量的庫,用來快速構建web組件。其核心是LitElement基類,可以提供響應式狀態(tài)、作用域樣式以及高效靈活的模版系統(tǒng)。盡管它也是一個基于原生web組件而封裝的庫,但它依然保留了web組件的所有特性。不必依賴框架便可以實現(xiàn)組件化,并且它的使用不受框架的制約,甚至可以沒有框架。

          基本特點

          • 類jsx/tsx語法。
          • 模版語法類似模版字符串的寫法。
          • 只支持原生css,但預編譯的樣式,需借助打包器編譯實現(xiàn)后,通過特定方式引入使用。
          • 支持ts。
          • 編程模型:OOP。
          • 單向數(shù)據(jù)流,支持MVVM,但無雙向綁定。
          • 組件狀態(tài)管理:使用內(nèi)置的@state與@property實現(xiàn)。
          • 職責范圍小,單純處理web組件的創(chuàng)建與使用。
          • 跨平臺跨框架。
          • 庫的體積較小,據(jù)官網(wǎng)說明,gzip壓縮混淆后只有5k左右。
          • Lit的html模版中,可以通過(.[屬性])的方式,進行自定義回調(diào)的傳遞(官網(wǎng)未說明,屬于hack方式)

          Lit的應用及基本原理

          Demo示例

          暫時無法在文檔外展示此內(nèi)容

          基本組成及應用

          基類

          基類(LitElement)是lit最核心的組成,它繼承了原生的HTMLElement類,并在此基礎上進行了豐富的擴展。包括響應式狀態(tài)、生命周期以及一些諸如控制器和混入等高級用法的提供。

          裝飾器

          可以理解為一些語法糖,用于修改類、類方法及屬性的特殊函數(shù)。

          • @customElement('my-element'),注冊自定義組件,相當于js中的
          window.customElements.define('my-element')。
          • @eventOptions({capture: true,passive:true,once:true}),事件監(jiān)聽配置,相當于js中的
          dom.addEventListener(eventName,func,{capture:?true,passive:true,once:true})。
          • @property(options?) test:string= 'Somebody',公共屬性狀態(tài)的聲明; 相當于js中的
          ??constructor()?{

          ????super();

          ????this.test?=?'Somebody';?

          ??}

          ??

          ??static?get?properties()?{

          ????return?{

          ??????test:?{type:?String},

          ????}

          ??}

          options是一個配置對象,其中包含:

          • attribute:表示聲明的property是否與組件中元素的attribute建立連接,false表示不建立,true表示建立,并且attribute的名字與property同名。為string時,表示建立并且attribute的名字為該字符串。(建立連接,表示該property與attribute相互映射。)

          • converter:表示處理property與attribute之間的轉換規(guī)則。

            • 默認時,attribute=》property,property為聲明時類型,attribute為string;
            • 為function時,處理attribute=》property;
            • 為object時,fromAttribute處理attribute=》property;toAttribute處理property=》attribute。
          • noAccessor:表示是否監(jiān)聽該屬性變化并自動更新,默認為false,表示監(jiān)聽并自動更新。為true時,表示不自動更新,需要開發(fā)者調(diào)用this.requestUpdate(propertyName, oldValue),來進行視圖更新。

          • reflect:表示是否將property的變化同步到attribute,默認false。false時,不會同步,盡管你設置了converter;為true時,會同步,根據(jù)converter的設置轉換。

          • type:表示類型聲明。

          • hasChanged:是一個函數(shù),參數(shù)為value 與oldValue,分別是屬性的新值與舊值。如果返回false,表示屬性相關的視圖不需更新;返回true則相反。

          • @state(options) protected _active = false, 內(nèi)部屬性狀態(tài)的聲明,本質(zhì)上也是一個property,同樣會觸發(fā)視圖的更新機制;相當于js中的
          constructor()?{

          ????super();

          ????this._active?=?false;

          ??}

          ??

          ??static?get?properties()?{

          ????return?{

          ??????_active:?{state:?true}

          ????}

          ??}

          options是一個配置對象,其中包含:

          • hasChanged:同上。
          • @query('todo-list') todoList!: TodoList;獲取單個dom元素,相當于js中的
          document.querySelector('todo-list');
          • @queryAll('todo-lists') todoLists!: TodoLists;獲取批量dom元素,相當于js中的
          document.querySelectorAll('todo-lists');
          • @queryAssignedNodes(slotName, flatten, selector),用來獲取對應slot元素的。

            • slotName:slot的name,string類型。
            • flatten:是否平鋪,boolean類型。
            • selector:是否過濾出當前選擇器的slot。
          • @queryAsync('todo-list'),同@query一樣,用來獲取單個dom的,只是@query是同步獲取,返回的是dom對象;@queryAsync是異步獲取,執(zhí)行時機在dom更新完成以后,執(zhí)行updateComplete的promise后執(zhí)行,返回的是一個promise對象。

          html模版

          我們來看這樣一段關于html模版的代碼

          。。。

          ??render()?{

          ????if(this.listItems.filter(item?=>?!item.completed).length?===?0)?{

          ??????return?html`<p>anything?was?done!p>`;

          ????}

          ????return?html`

          ??????<ul>

          ????????
          ${this.listItems.map((item)?=>?html`

          ??????????<li

          ??????????????class=
          ${item.completed???'completed'?:?''}

          ??????????????@click=
          ${()?=>?this.toggleCompleted(item)}>

          ????????????
          ${item.text}

          ??????????li>
          `
          ,

          ??)}

          ??????ul>


          ????`;

          ??}

          ??。。。

          可以看到Lit的html渲染是在render函數(shù)中進行的。通過html方法和模版字符串的結合,實現(xiàn)渲染。并且語法類似jsx/tsx??梢灾苯釉趓ender函數(shù)和html模版中寫js邏輯。非常的靈活與方便。

          樣式

          • Lit本身只支持原生css,使用css方法添加樣式,方式如下:
          //只有一組style

          import?{customElement,?css}?from?'lit-element';

          @customElement('my-element')

          export?class?MyElement?extends?LitElement?{

          ??static?styles?=?css`

          ????p?{

          ??????color:?green;

          ????}

          ??`
          ;

          ??。。。

          }



          //多組style

          import?{css}?from?'lit-element';

          static?styles?=?[?

          ????css`h1?{

          ??????color:?green;

          ????}?`
          ,?

          ????css`h2?{

          ??????color:?red;

          ????}`


          ];



          //引入樣式文件

          import?{css,unsafeCSS}?from?'lit-element';

          import?style?from?'./my-elements.less';//需要使用編譯工具編譯后倒入

          ??static?styles?=?[

          ????css`:host?{

          ????width:500px;

          ??}`
          ,

          ????css`${unsafeCSS(style)}`

          ????];
          • Lit允許在css中書寫表達式

          這就意味著你可以在某個ts文件中聲明一組樣式,然后引入到多個文件中使用:

          //樣式中使用表達式

          static?get?styles()?{

          ??const?mainColor?=?'red';

          ??return?css`

          ????div?{?color:?
          ${unsafeCSS(mainColor)}?}

          ??`
          ;

          }
          • 動態(tài)樣式

          可以想vue或react中一樣,動態(tài)的使用class和style。

          import?{customElement,?property,LitElement,?html,?css}?from?'lit-element';

          import?{classMap}?from?'lit/directives/class-map.js';

          import?{styleMap}?from?'lit/directives/style-map.js';



          @customElement('my-element')

          export?class?MyElement?extends?LitElement?{

          ??@property()

          ??classes?=?{?someclass:?true,?anotherclass:?true?};

          ??@property()

          ??styles?=?{?color:?'lightgreen',?fontFamily:?'Roboto'?};

          ??protected?render()?{

          ????return?html`

          ??????<div?class=
          ${classMap(this.classes)}?style=${styleMap(this.styles)}>

          ????????content

          ??????div>


          ????`;

          ??}

          }

          slot插槽

          • 概念

          關于插槽的概念理解,其實可以直接類比vue中的slot插槽,因為lit本身是一個純js庫,所以lit的插槽完全是來源于原生web component技術所提供的規(guī)范,而vue的slot功能,參考來源正是原生的slot。

          • 注意??

            • 默認情況下,如果一個自定義元素有存在shadow dom,那么它的子元素是不會渲染的。
          ??test-word="test-word"?testWord="testWord">

          ????
          我是子元素


          ??
          • 預置slot中的內(nèi)容,可以在對應子元素渲染前,起到兜底的作用。
          • slot的使用

            • 匿名slot,只需要預置一個slot,那么所有的子元素都可以被安排呈現(xiàn)。
          ??//html

          ????"test-word"?testWord="testWord">

          ????
          我是子元素1</div>

          ????
          我是子元素2div>

          ????
          我是子元素3</div>

          ??-element>

          ??//ts

          ??render()?{

          ????return?html`

          ??????<slot>slot>


          ????`
          ;

          ??}
          • 具名slot,只根據(jù)對應的name呈現(xiàn),其他的被忽略
          ??//html

          ??"test-word"?testWord="testWord">

          ????"child1">我是子元素1</div>

          ????
          我是子元素2div>

          ????
          我是子元素3</div>

          ??-element>

          ??

          ??//ts

          ????render()?{

          ????return?html`

          ??????<slot?name="child1">slot>


          ????`
          ;

          ??}
          • slot的生命周期

          可以通過在slot元素上面綁定slotchange事件,來獲取slot被插入或刪除的時機,以及對應的事件對象。

          ??//html

          ??"test-word"?testWord="testWord">

          ????"child1">我是子元素1</div>

          ??-element>

          ??//ts

          ??

          ??handleSlotchange(e:Event)?{

          ????console.log(e);

          ??}



          ??render()?{

          ????return?html`

          ??????<slot?name="child1"??@slotchange=
          ${this.handleSlotchange}>slot>

          ????`
          ;

          ??}

          }

          事件通信

          在Lit中,也是內(nèi)置了事件通信的邏輯。事件通信主要是兩部分組成,注冊監(jiān)聽和調(diào)度觸發(fā)。

          • 注冊監(jiān)聽,采用@[事件名]的方式,在組件上注冊事件;使用e.detail來回去通信內(nèi)容。
          。。。

          ??private?addList(e:?CustomEvent){

          ????this.listItems?=?[...this.listItems,{

          ??????text:e.detail,

          ??????completed:?false,

          ????}?as?ToDoItem];

          ????this.todoList.requestUpdate();

          ??}

          。。。

          ??render()?{

          ????return?html`

          ????。。。

          ??????<controls-area?@addList=
          ${this.addList}>controls-area>

          ????。。。

          ????`;

          ??}

          ??。。。
          • 調(diào)度觸發(fā),使用dispatchEvent進行調(diào)度,使用CustomEvent構造函數(shù)及固定的結構來實例化通信傳輸?shù)氖录ο蟆?/section>
          ...

          ??private?sendText(){

          ????const?options?=?{

          ??????detail:?this.inputText,

          ????};

          ????this.dispatchEvent(new?CustomEvent('addList',?options));

          ????this.inputText?=?'';

          ??}

          ...

          生命周期及更新流程

          Lit采用批量更新的方式來提高性能和效率。一次設置多個屬性只會觸發(fā)一次更新,然后在微任務定時異步中執(zhí)行。

          狀態(tài)更新時,只渲染 DOM 中發(fā)生改變的部分。由于 Lit 只解析并創(chuàng)建一次靜態(tài) HTML ,并且后續(xù)只更新表達式中更改的值,所以更新非常高效。

          • 更新流程

            • 當屬性被set時,屬性的setter被觸發(fā)(屬性的監(jiān)聽通過defineProperty完成)。
            • 然后將觸發(fā)組件的requestUpdate。
            • 此時若該屬性設置了hasChanged函數(shù),那么等待該函數(shù)返回值來決定是否繼續(xù)更新。若沒有設置hasChanged函數(shù),則直接對比新舊值。
            • 新舊值不一致時,觸發(fā)異步更新,調(diào)用組件的update方法。如果發(fā)現(xiàn)已經(jīng)觸發(fā)了一次更新,那么執(zhí)行最后一個更新。
            • 觸發(fā)更新后,將更新的property再次映射到attribute中,并渲染html。
          • 生命周期

          lit中的生命周期分為兩類,一類是原生組件化提供的生命周期,一般不需要開發(fā)者主動去使用。另一類是lit的狀態(tài)更新提供的生命周期,如下:

          • requestUpdate

          執(zhí)行了requestUpdateInternal方法,并返回了更新結果的promise。requestUpdateInternal方法中主要進行了兩個操作,一個是將更新的屬性存在一個map中,以備后續(xù)使用。另一個是進行一些比較判斷,決定是否調(diào)用_enqueueUpdate方法。而_enqueueUpdate調(diào)用了performUpdate方法。

          • performUpdate

          performUpdate方法中執(zhí)行了shouldUpdate,shouldUpdate返回false則中斷,若返回true,則依次執(zhí)行willUpdate、update、firstUpdated和updated。

          • willUpdate

          ts中不存在willUpdate,是js的polyfill-support 的覆蓋點,源碼中函數(shù)內(nèi)容為空。

          • Update

          主要執(zhí)行了_propertyToAttribute函數(shù),將property向attribute映射,覆蓋render渲染出來的html。

          • Render

          render函數(shù)只執(zhí)行一次,用來解析并創(chuàng)建靜態(tài) HTML。

          • firstUpdated

          是一個覆蓋點,源碼中為空。元素首次更新完畢觸發(fā),只執(zhí)行一次。此方法中設置屬性,會在本次更新完畢后再次觸發(fā)更新。

          • updated

          是一個覆蓋點,源碼中為空。元素每次更新完畢觸發(fā)。此方法中設置屬性,會在本次更新完畢后再次觸發(fā)更新。

          • updateComplete

          是函數(shù)_getUpdateComplete的返回值,本質(zhì)上是一個promise對象,表示當前更新全部完畢。

          ?protected?_getUpdateComplete()?{

          ????return?this.getUpdateComplete();

          ??}

          ?protected?getUpdateComplete()?{

          ????return?this._updatePromise;

          ?}

          高階應用

          指令

          如上面的動態(tài)樣式一樣,classMap和styleMap屬于應用在html模版中的指令。開發(fā)者可以直接使用內(nèi)置指令進行開發(fā);也可以根據(jù)自己的需要,進行自定義指令的開發(fā)。

          • 內(nèi)置指令

          • classMap - 將類列表設置為基于對象的元素

          • styleMap - 將樣式屬性列表設置為基于對象的元素
          • repeat - 將值從可迭代對象渲染到 DOM 中
          • templageContent- 呈現(xiàn)