Web組件構建庫-Lit
大廠技術??堅持周更??精選好文
認識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)元素的內(nèi)容 unsafeHTML - 將字符串呈現(xiàn)為 HTML unsafeSVG - 將字符串呈現(xiàn)為 SVG cache - 更改模板時緩存呈現(xiàn)的 DOM guard - 僅在其依賴項之一發(fā)生變化時重新更新模板 ifDefined - 如果值已定義,則設置該屬性,如果未定義,則刪除該屬性 live - 采用嚴格的‘===’檢查實時DOM值與表達式的值,不相等便觸發(fā)更新。 until - 呈現(xiàn)占位符內(nèi)容,直到一個或多個Promise解決 asyncAppend- 將 AsyncIterable 的promise結果插入到 DOM 中 asyncReplace- 將 AsyncIterable 的promise結果替換到 DOM 中 ref - 獲取dom節(jié)點 自定義指令
自定義指令的功能很強大,盡管在使用中看起來只是調(diào)用了一個函數(shù),但實際上,內(nèi)部包含了自己的生命周期(constructor、render、update),不僅如此,指令還能獲得與它關聯(lián)的底層 DOM 的特殊訪問。
這里我們實現(xiàn)一個簡單的指令:
首先聲明一個繼承了Directive類的自定義指令類。 內(nèi)部定義生命周期的鉤子函數(shù)render,將傳入的字符串修改后返回。 使用directive實例化,并向外暴露。
//自定義指令的文件
import?{Directive,?directive}?from?'lit/directive.js';
class?FormatStr?extends?Directive?{
??render(test:string)?{
????return?`${test}!!!`;
??}
}
export?const?formatStr?=?directive(FormatStr);
//使用自定義指令的文件
import?{formatStr}?from?'../directives/formatStr';
import?{html}?from?'lit';
。。。
render(){
????html`<div>${formatStr('hellow')}div>`
}
。。。

混和
混合本身的作用,是為了在類之間共享代碼。而類混合,本質(zhì)上是屬于原生類的一種行為,由于Lit是一個原生的js庫,所以它可以拿來直接使用。目的也很簡單,為了封裝抽象。在Lit中使用類混合,我們可以在復用代碼的同時,做一些定制化的擴展。下面我們實現(xiàn)一個混合。
這里我們聲明一個混合類的方法,使得調(diào)用這個函數(shù)后生成的類,擁有公共的方法,就是在組件連接主文檔后進行一次打印,打印的結果,是當前組件自己的name屬性。
/*?eslint-disable?no-unused-vars?*/
import?{LitElement}?from?'lit';
type?Constructor?=?new?(...args:?any[])?=>?T;
export?const?TestMixin?=?extends
?Constructor>(superClass:?S)?=>?{
??class?MyMixinClass?extends?superClass?{
????constructor(...args:?any[])?{
??????super();
??????this.name?=?'TestMixin';
????}
????name:string;
????connectedCallback()?{
??????super.connectedCallback();
??????setTimeout(()=>{
????????console.log(this.name);
??????},3000)
????}
??}
??return?MyMixinClass?as?S;
}
這里是使用的文件:
//TodoList.ts
export?class?TodoList?extends?TestMixin(LitElement)?{
??constructor()?{
????super();
????this.name?=?'TodoList';
??}
??name:string;
??}
?
?//ControlsArea.ts
?export?class?ControlsArea?extends?TestMixin(LitElement)?{
??constructor()?{
????super();
????this.name?=?'ControlsArea';
??}
??name:string;
??}
這里是打印結果,分別會在連接到主文檔3s后打印各自的name。

控制器
控制器是Lit中,又一個封裝抽象的概念,它區(qū)別于組件和混合。它沒有視圖,也不封裝視圖;它擁有同宿主綁定的生命周期,但是不存在狀態(tài)更新的機制。相對于混合的代碼共用,它更像是實現(xiàn)功能共用。
與宿主交互的相關方法:
addController:同宿主綁定。 removeController:同宿主解除綁定。 requestUpdate:更新宿主視圖。 updateComplete:獲取宿主更新完畢的promise。
有四個可以與宿主綁定的生命周期:
hostConnected:宿主連接主文檔時執(zhí)行。 hostUpdate:宿主將property向attribute映射完畢(update)后,render之前執(zhí)行。 hostUpdated:在組件每次更新完畢(updated)后執(zhí)行。 hostDisconnected:宿主與主文檔斷開連接時執(zhí)行。
實現(xiàn)一個簡單的控制器:
//聲明控制器
import?{ReactiveControllerHost}?from?'lit';
export?class?MouseController?{
??private?host:?ReactiveControllerHost;
??pos?=?{x:?0,?y:?0};
??_onMouseMove?=?({clientX,?clientY}:?MouseEvent)?=>?{
????this.pos?=?{x:?clientX,?y:?clientY};
????this.host.requestUpdate();
??};
??constructor(host:?ReactiveControllerHost)?{
????this.host?=?host;
????host.addController(this);
??}
??hostConnected()?{
????window.addEventListener('mousemove',?this._onMouseMove);
??}
??hostDisconnected()?{
????window.removeEventListener('mousemove',?this._onMouseMove);
??}
}
//使用控制器
import?{MouseController}?from?'../controller/mouseController';
export?class?ControlsArea?extends?TestMixin(LitElement)?{
??constructor()?{
????super();
??}
??private?mouse?=?new?MouseController(this);
??render()?{
????return?html`
????<pre>
????????x:?${this.mouse.pos.x?as?number}
????????y:?${this.mouse.pos.y?as?number}
??????pre>
????`;
??}
}
生態(tài)相關
Github star issue數(shù)量(未解決/總數(shù)) npm下載量 被依賴數(shù)量 維護團隊 開源協(xié)議 文檔 年齡 背書公司 8.7k
171/2084

lit團隊 BSD-3-Clause
https://lit.dev/ 4 無
路由
Lit官方并未提供路由,但是社區(qū)提供了:
lit-element-router傳送門:https://www.npmjs.com/package/lit-element-router
共享狀態(tài)管理
Lit官方并未提供共享狀態(tài)管理,但是社區(qū)提供了:
lit-element-state傳送門:https://www.npmjs.com/package/lit-element-state
開發(fā)插件(vscode)
語法高亮(lit-plugin)



高亮前 高亮后
代碼片段(LitElement Snippet)



js下代碼片段提示 ts下代碼片段提示
SSR
支持ssr,并且官方提供了對應的工具包:
@lit-labs/ssr :https://www.npmjs.com/package/@lit-labs/ssr
github地址
https://github.com/lit/lit/
測試工具
lit是標準的js工具庫,可以使用任何js測試工具。
https://lit.dev/docs/tools/testing/
依賴Lit的組織及項目

同類框架/庫比較

可以看到lit的下載量遙遙領先。當然,這并不意味著lit就是最好的,畢竟不同的場景,有不同的選型。但至少可以確定一點,lit的應用場景相對是比較多的,這也是下載量不斷飆升的原因。
依賴Lit的開源組件庫
名稱 官網(wǎng) github 開源協(xié)議 背書公司 Spectrum Web Components https://opensource.adobe.com/spectrum-web-components/ https://github.com/adobe/spectrum-web-components Apache License adobe Momentum UI Web Components https://momentum-design.github.io/momentum-ui/?path=/story/components-accordion--accordion https://github.com/momentum-design/momentum-ui/tree/master/web-components MIT cisco material-components https://github.com/material-components/material-components-web#readme https://github.com/material-components/material-web Apache License 2.0 google frontend ui https://github.com/home-assistant/frontend/blob/dev/README.md https://github.com/home-assistant/frontend Apache License home-assistant carbon-web-components https://web-components.carbondesignsystem.com/ https://github.com/carbon-design-system/carbon-web-components Apache License 2.0 IBM Lion Web Components https://lion-web.netlify.app/ https://github.com/ing-bank/lion MIT License ING PWA Starter https://github.com/pwa-builder/pwa-starter/blob/main/README.md https://github.com/pwa-builder/pwa-starter MIT License microsoft
以上只提供了部分背書公司較知名的組件庫(均為國外組件庫,國內(nèi)目前暫無知名的相關組件庫)。用以觀察借鑒,規(guī)避踩坑。
其他相關參考
lit項目所有者:https://github.com/orgs/lit/people =》https://github.com/e111077
lit項目被依賴關系:https://github.com/lit/lit/network/dependents
Lit-element npm包地址:https://www.npmjs.com/package/lit-element
框架對比網(wǎng)站:https://www.npmtrends.com/lit-element-vs-svelte-vs-@stencil/core
參考資料
[1] 你真的了解Web Component嗎: https://juejin.cn/post/7010580819895844878
- END -?? 謝謝支持
以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^
喜歡的話別忘了?分享、點贊、收藏?三連哦~。
歡迎關注公眾號?趣談前端?收貨大廠一手好文章~

點個在看你最好看
瀏覽
1評論圖片表情
婷婷久久小说网
|
亚州午夜双飞
|
亚洲第一页面
|
欧美强开小嫩苞
|
天天爱天天射
|
