柚子快報激活碼778899分享:Google的新前端框架Lit
柚子快報激活碼778899分享:Google的新前端框架Lit
Google的新框架 Lit ,有 Google 加持的框架引起了我的興趣,就去簡單的了解了一下,簡單的給大家分享一下學(xué)習(xí)成果。由于Lit框架還在快遞迭代中,文中講到的一些代碼實現(xiàn)很可能已經(jīng)重構(gòu),感興趣的同學(xué),可以去翻一下Lit源碼。
什么是 Lit
Lit 是一個基于 Web-Component 構(gòu)建的前端框架,前身基本可以理解為即 Polymer , Lit 提供了如下具有競爭力的特性
基于 Web-Component 的更高層封裝,提供了現(xiàn)代前端開發(fā)習(xí)慣的響應(yīng)式數(shù)據(jù),聲明式的模版,減少了web component的一部分樣板代碼. 小。運行時僅有5K 性能強悍。規(guī)避了 VDOM 的一些弊端,更新時僅處理 UI 中的異步部分(可以理解成僅處理響應(yīng)式的部分) 兼容性較好。因為 web-component 是 HTML 的原生能力,也就代表著 web-component 可以在任何使用 HTML 的地方使用,框架無關(guān)。
小和框架無關(guān)是促使我關(guān)注這個框架的一個重點(svelte也是,有時間再說,學(xué)不動了),因為對于一些通用業(yè)務(wù)代碼來說, 運行時盡可能的小 和 框架無關(guān) 是最核心的兩個技術(shù)選型指標(biāo)。
什么是 Web-Component
Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.
我個人認為,組件化是現(xiàn)在前端快速發(fā)展和規(guī)模壯大的一個比較重要的原因,想想寫 JQ 的年代,HTML 代碼基本都長這個樣子
html
代碼解讀
復(fù)制代碼
哪怕后面出現(xiàn)了模版語法,狀況也沒有變得更好(與服務(wù)端協(xié)作共用模版 -> JSP or 要在JS中封裝方法,通過模版語法注入 -> handlebars),單純的loop循環(huán)渲染可能能方便的解決DOM復(fù)用的問題,但是跨層級的組件復(fù)用仍然是一個問題。
因此任何一個想要進一步發(fā)展的前端框架技術(shù),組件化是必不可少的一步。 Web-Component 就是這樣一個瀏覽器原生支持的創(chuàng)建可重用元素(自定義組件)的能力,而 Lit 則是基于 Web-Component 構(gòu)建的。
那我們需要先了解下 Web-Component
Web-Component 的簡單開發(fā)流
Create a class in which you specify your web component functionality, using the ECMAScript 2015 class syntax Register your new custom element using the CustomElementRegistry.define() method, passing it the element name to be defined, the class or function in which its functionality is specified, and optionally, what element it inherits from. If required, attach a shadow DOM to the custom element using Element.attachShadow() method. Add child elements, event listeners, etc., to the shadow DOM using regular DOM methods. If required, define an HTML template using and
我們先了解 Web-Component 的兩個核心點,了解了這兩個屬性就可以構(gòu)建你自己的組件了
Custom Element 自定義元素
首先,我們需要通過瀏覽器提供的 **CustomElementRegistry** ****接口實例注冊自定義元素,實例被掛載在 window.customElements 上
CustomElementRegistry的 define 方法可以用來注冊一個自定義的元素,有兩種類型可選
自定義元素 :獨立元素,行為完全由開發(fā)者定義。 自定義內(nèi)置元素 :這些元素繼承并擴展內(nèi)置HTML元素
API如下
javascript
代碼解讀
復(fù)制代碼
customElements.define(name, constructor, options);
name 就是你自定義的元素名稱(符合 DOMString 標(biāo)準(zhǔn),必須帶短橫線),以上述為例,可以通過
這么看來, Web-Component 的邏輯核心就在constructor上了,我們?nèi)绾味x一個自己的 component 呢? 不同于 React 和 Vue,可以在 render 方法中書寫 JSX 或者模版語法來創(chuàng)建一顆 VDOM 樹來定義組件結(jié)構(gòu)。 Web-Component 并沒有提供可以用來書寫模版的方式(但這也代表著他可以使用任何模版語法)。通常是使用常規(guī)的 DOM 操作來在 constructor 中創(chuàng)建你的組件結(jié)構(gòu)。舉個栗子:
javascript
代碼解讀
復(fù)制代碼
class MyComponent extends HTMLElement { constructor() { super() const wrapper = document.createElement('span'); wrapper.setAttribute('class','wrapper'); const info = document.createElement('span'); info.setAttribute('class','info'); info.textContent = this.getAttribute('text') || 'default'; this.appendChild(wrapper); wrapper.appendChild(info); } }
上面這段代碼創(chuàng)建了一個根據(jù)雙層span嵌套的文本節(jié)點
html
代碼解讀
復(fù)制代碼
當(dāng)然,單純的創(chuàng)建UI沒有什么意思,我們來思考下,現(xiàn)代框架還提供了什么能力?
響應(yīng)式!事件綁定!lifecycle!
OK,安排。
先說比較簡單的事件綁定,既然 DOM 都是內(nèi)部創(chuàng)建的,那么綁定事件也是輕而易舉(注意this指向)
javascript
代碼解讀
復(fù)制代碼
class extends HTMLElement { constructor() { super() this.handleClick = this.handleClick.bind(this) const wrapper = document.createElement('span'); wrapper.setAttribute('class','wrapper'); const info = document.createElement('span'); info.setAttribute('class','info'); info.textContent = this.getAttribute('text') || 'default'; this.appendChild(wrapper); wrapper.appendChild(info); const button = this.querySelector('#button'); button.addEventListener('click', this.handleClick) } handleClick () { this.parentNode.removeChild(this) } }
html
代碼解讀
復(fù)制代碼
上述代碼創(chuàng)建在之前的文本基礎(chǔ)上,多了一個按鈕,點一下這個按鈕就會把整個 custom element 移除掉(當(dāng)然,這個按鈕也可以由組件自己創(chuàng)建)
然后我們再來說 lifecycle ,customeElemnts.define接受的構(gòu)造函數(shù)中,允許開發(fā)者定義如下幾個生命周期,會在相關(guān)的時機被調(diào)用
connectedCallback :當(dāng) custom element 首次被插入文檔DOM時,被調(diào)用。 disconnectedCallback :當(dāng) custom element 從文檔DOM中刪除時,被調(diào)用。 adoptedCallback :當(dāng) custom element 被移動到新的文檔時,被調(diào)用。 attributeChangedCallback :當(dāng) custom element 增加、刪除、修改自身屬性時,被調(diào)用
簡單來說,就是
componentDidMount componentWillUnmount Not exist(這個我還沒測試出什么場景會用出現(xiàn)) componentDidUpdate
因為沒有 state 這個概念,所有的組件內(nèi)部屬性的變化監(jiān)聽都需要我們手動處理,心智負擔(dān)可能會略重一些。 當(dāng)然也可以把他們作為自定義元素的屬性,通過 ~~attributeChangedCallback~~ 處理
順路,有了屬性變化的回調(diào)響應(yīng)式也就出來了(當(dāng)然,只是響應(yīng)式的基礎(chǔ),屬性變化并不會直接作用到內(nèi)部渲染邏輯上,心智負擔(dān) +1 ) 如果需要在元素屬性變化后,觸發(fā)回調(diào)函數(shù),必須通過定義 observedAttributes() get函數(shù)來監(jiān)聽這個屬性
html
代碼解讀
復(fù)制代碼
看下代碼,這里定義了一個帶輸入框的組件,輸入框輸入后,會重新渲染 componnet 內(nèi)部的文本。同時監(jiān)聽 style 屬性,變化后打印出來(無意義的demo +1)
Shadow DOM 影子DOM
Shadow DOM 的好處有很多。 Shadow DOM 主要的作用在于,“他可以將獨立的一個DOM(style標(biāo)簽也屬于)附加到元素上(這個DOM是隱藏的),且不會影響外層的樣式,這給web component帶來了十分強大的封裝能力,能夠完全的將組件的結(jié)構(gòu),樣式和行為動作對外隱藏起來,對外隔離”]
有一個需要關(guān)注的概念 - Shadow boundary Shadow boundary 指的是 Shadow DOM 結(jié)束的地方,也是常規(guī) DOM 開始的地方。整個 shadow dom 內(nèi)的樣式、元素都不會對外影響超過 shadow boundary 的范圍。
Shadow DOM 不是一個新事物——在過去的很長一段時間里,瀏覽器用它來封裝一些元素的內(nèi)部結(jié)構(gòu)。以一個有著默認播放控制按鈕的
那么,如何使用呢? 有一個核心API, Element.attachShadow({mode: }) ,可以將一個 shadow root 附加到任何一個元素上,mode 的取值有 open 或者 closed。區(qū)別在于能否從外部獲取到 shadowDOM 的結(jié)構(gòu)。想要更深入的了解 open 和 close 的區(qū)別可以參考 blog.revillweb.com/open-vs-clo… 這篇文章,這里就不展開描述了。
那么,拿上邊最簡單的那個例子做一下改造
javascript
代碼解讀
復(fù)制代碼
customElements.define('my-component', class extends HTMLElement { constructor() { super() const shadow = this.attachShadow({mode: 'open'}); const info = document.createElement('span'); info.setAttribute('class','info'); info.textContent = this.getAttribute('text') || 'default'; // this.appendChild(info) shadow.appendChild(info); } })
這樣就創(chuàng)建了一個基于 shadowDOM 的自定義元素了,看起來沒什么差別,我們再添加一些自定義的樣式試試
javascript
代碼解讀
復(fù)制代碼
customElements.define('my-component', class extends HTMLElement { constructor() { super() const shadow = this.attachShadow({mode: 'open'}); const info = document.createElement('span'); info.setAttribute('class','info'); info.textContent = this.getAttribute('text') || 'default'; const style = document.createElement('style') style.textContent = ` span { color: red; } ` // this.appendChild(info) shadow.appendChild(style); shadow.appendChild(info); } })
可以嘗試在頁面的其他地方也添加一些span標(biāo)簽,但是你會發(fā)現(xiàn),只有 下面的 span 標(biāo)簽生效了紅色的樣式。
上述兩個屬性可以創(chuàng)建一個不受外部影響,且擁有內(nèi)部JS運行邏輯、擁有獨立CSS的自定義元素(也就是Web Component) 我覺得大家已經(jīng)開始在吐槽,這種類JQ的寫法簡直是異類,DOM復(fù)雜起來之后就很難整了。那么如何組裝更復(fù)雜的dom,難道無解了么?不,那么這里需要提到另外一個屬性
Template & Slot
template 元素是瀏覽器一直以來都支持的一個特性,template 中的內(nèi)容在渲染 HTML 到屏幕上的時候不會顯示出來,需要通過 Javascript 獲取到模版后才能實例化,并渲染到頁面上。 那么,我們可以把 template 作為一個可以儲存在文檔中的內(nèi)容片段,然后在組件渲染的時候把 template 填充到 Web-Component 的 shadow dom 里面。還是拿上邊的那個例子做修改
html
代碼解讀
復(fù)制代碼
12312312
javascript
代碼解讀
復(fù)制代碼
customElements.define('my-component', class extends HTMLElement { constructor() { super() const template = document.getElementById('my-paragraph'); const templateContent = template.content.cloneNode(true); const shadow = this .attachShadow({mode: 'open'}) .appendChild(templateContent); } })
這樣以來,是不是有點 Vue 那個意思了,但是還缺點什么,my-component 上之前可以讀取 text 屬性填充到 span.info 里面,現(xiàn)在好像沒有這個能力了。
這個時候需要請出我們的 slot 槽來做這件事
HTML
Slot 通過 name 屬性標(biāo)示,放置的位置表示他在模版中的位置,當(dāng)有另外一個元素定義了同名的 slot 屬性,那么這個元素就會被替換到模版中。我們修改下上邊的那個例子
html
代碼解讀
復(fù)制代碼
default
javascript
代碼解讀
復(fù)制代碼
customElements.define('my-component', class extends HTMLElement { constructor() { super() const template = document.getElementById('my-paragraph'); const templateContent = template.content.cloneNode(true); const shadow = this .attachShadow({mode: 'open'}) .appendChild(templateContent); } })
當(dāng)然這里只是最為簡單的用法,但至此,基本上 Web-Component 你就算入門了。整體寫起來難度也不算太高,但還是有不少值得吐槽的地方 (更多關(guān)于Web Component) 。那么我們來看看 Lit 做了啥,能不能讓 Web-Component 變得更好用些
Lit做了啥
看下我們剛才說到的 Web-Component 里面的幾個槽點
響應(yīng)式僅有回調(diào),無法自動映射到UI上 沒有 state 內(nèi)部狀態(tài),自己維護的狀態(tài)無法直接監(jiān)聽變化 沒有模版語法(可以用 slot 和 template)
明確一點,在學(xué)習(xí) Lit 的過程中,可以認為沒有 state 這個概念(實際上有,理解為私有的 reactive properties),只有名為 reactive properties 的成員屬性??梢院唵蔚睦斫獬捎质?state,又是 props。
那么現(xiàn)在問題轉(zhuǎn)變成了
如何響應(yīng)reactive properties的變化,并應(yīng)用到UI上 如何解決模版語法
Lit 用了兩個個核心庫來解決這個問題,分別是 lit-element 和 lit-html
Lit-html
lit-html 是 Lit 的核心邏輯,可以理解為 Literal Html ,他異于JSX創(chuàng)造了另外一種高性能的字符流HTML模版引擎。 Lit選擇了直接繼承Polymer的LitHTML項目,并將整體框架重命名為 Lit 我們知道 jsx 是需要編譯的它的底層最終還是 createElement ....。而 lit-html 就不一樣了,它是基于 tagged template 的,使得它不用編譯就可以在瀏覽器上運行,并且和 HTML Template 結(jié)合想怎么玩怎么玩,擴展能力更強。下面我們展開來看。
lit-html 提供了兩個核心方法 render 和 html
lit-html.html
javascript
代碼解讀
復(fù)制代碼
html`
${content}
`這個是es6的原生語法 - 帶標(biāo)簽的模板字符串 (tagged template),并不是什么magic,html 這個函數(shù)會接受到如下的參數(shù)
javascript
代碼解讀
復(fù)制代碼
type taggedFunc = (strings: string[], ...values: any[]) => any; // 上邊的那個段代碼接收到的參數(shù)就是 // ['
', '
], content經(jīng)過 lit-html 的修飾上面這段代碼最終會構(gòu)造一個 Template Result 對象,形如
typescript
代碼解讀
復(fù)制代碼
declare class TemplateResult { readonly strings: TemplateStringsArray; readonly values: readonly unknown[]; readonly type: string; // html or svg readonly processor: TemplateProcessor; constructor(strings: TemplateStringsArray, values: readonly unknown[], type: string, processor: TemplateProcessor); getHTML(): string; getTemplateElement(): HTMLTemplateElement; } const templateResult = { strings: ['
', '
'], value: [content] type: 'html' }這里需要注意一下 getHTML 和 getTemplateElement 方法,這兩個方法可以將strings轉(zhuǎn)化成為一個 標(biāo)記,也就是上面提到的 template
javascript
代碼解讀
復(fù)制代碼
const template = (title, content, className) => html`
${title}
htmlbars
代碼解讀
復(fù)制代碼
簡單的解釋一下,這個過程就是逐個處理strings中的數(shù)據(jù),根據(jù)不同的情況
Attribute Node Comment
拼接成一個完整的字符串,然后innerHTML插入到創(chuàng)建好的template標(biāo)記中。 Q:如何區(qū)分代碼中真正的comment?
lit-html.render
現(xiàn)在我們有了通過標(biāo)簽?zāi)0娴玫降?TemplateResult (一個純值對象),接下來需要調(diào)用 render 方法去渲染模版到頁面上,先看API render(templateResult, container, options?) render 接收一個 templateResult實例 和 container 渲染容器來完成一次渲染,這里分為首次渲染和更新渲染。
首次渲染
先創(chuàng)建一個 NodePart 對象(繼承自Part,可以理解為節(jié)點的構(gòu)造器controller,這個是核心實現(xiàn),暫時不展開,后面來看),然后調(diào)用 NodePart 實例的 appendInto 方法,在渲染容器中加入兩個 comment ,同時記錄了兩個 comment 的引用。后續(xù) NodePart 會把 DOM 渲染到這兩個 comment 中間
html
代碼解讀
復(fù)制代碼
然后會調(diào)用 part.commit 方法,將內(nèi)容渲染到容器中 commit分為了幾種情況
directive primitive(原始類型) templateResult node Iterable 清空
根據(jù)前面的邏輯,第一次一定會直接走進 templateResult 的分支,這里的邏輯可以簡單這么描述, 通過 Factory ,使用 TemplateResult 中的模版部分 strings 創(chuàng)建一個 Template 對象(中間產(chǎn)物), Factory 這里做了一層緩存,如果使用 TemplateResult 的模版(strings)有現(xiàn)成的模版的話,直接使用現(xiàn)成的模版,如果沒有,則重新創(chuàng)建。 在后續(xù)調(diào)用 render方法時,相同的模版(strings 值與第一次調(diào)用時是完全一致)是重用第一次的Template的,可以理解為編譯時期就確定的一個常量值,而變化的只有 value 數(shù)組
typescript
代碼解讀
復(fù)制代碼
export declare class Template { readonly parts: TemplatePart[]; readonly element: HTMLTemplateElement; } export type TemplatePart = { readonly type: 'node'; index: number; } | { readonly type: 'attribute'; index: number; readonly name: string; readonly strings: ReadonlyArray
先用TemplateResult的模版(string)找有沒有現(xiàn)成的模版,如果有,直接復(fù)用 如果沒有,則檢查keyString的模版中有沒有 模版.join markerKey的引用(markKey means lit-7227407027270176) 如果還是沒有,則創(chuàng)建一個Template實例,并且將Template 使用模版 和 keyString緩存起來
緩存流程不展開講解,如果有興趣自己看一下
Template 對象中分為 parts 和 element,element就是TemplateResult轉(zhuǎn)化出來的 ,parts部分,是在遍歷(dom walker)的時候生成的。處理流程簡化理解
如果是Node節(jié)點
判斷是否有attribute,且屬性名有特殊標(biāo)記,有的話,移除template上的屬性,并往part push一個 {type: 'attribute', index, name, strings: statics} 的結(jié)構(gòu),index是當(dāng)前的walker下標(biāo),name是屬性名,strings是這個屬性的插值前后字符 如果是Comment節(jié)點
如果comment的內(nèi)容等同于marker -(這里可以和真正的comment區(qū)分開),然后往part中推入一個node節(jié)點 {type: 'node', index}
如果是第一個節(jié)點或者前面一個節(jié)點已經(jīng)是一個part的標(biāo)記了,會先在當(dāng)前節(jié)點前添加一個空的comment節(jié)點,
htmlbars
代碼解讀
復(fù)制代碼
處理完成后
javascript
代碼解讀
復(fù)制代碼
{ element: template parts: [ {type: "attribute", index: 1, name: "class", strings: ["", ""]}, {type: "node", index: 3}, {type: "node", index: 7}, ] } // templatee也會會簡化成如下結(jié)構(gòu)
可以理解 Template 是一個已經(jīng)成型的 DOM 模版,他擁有完整的 DOM 和需要插值的位置定位,但他還沒渲染到 DOM 上
接下來檢查當(dāng)前的 Template 是否已經(jīng)創(chuàng)建了 TemplateInstance 實例,如果沒有,實例化一個 TemplateInstance
typescript
代碼解讀
復(fù)制代碼
class TemplateInstance { private readonly __parts; readonly processor: TemplateProcessor; readonly options: RenderOptions; readonly template: Template; constructor(template: Template, processor: TemplateProcessor, options: RenderOptions); update(values: readonly unknown[]): void; _clone(): DocumentFragment; }
TemplateInstance 會通過創(chuàng)建 fragment ; 然后遍歷 parts ,根據(jù) TemplatePart 字面量的類型,分別創(chuàng)建 NodePart 和 AttributePart 實例。
最終調(diào)用 TemplateInstance 實例的 update 方法,這個方法會逐個調(diào)用 Part 實例的 setValue (真實的值)和 commit (渲染方法)方法,至此,循環(huán)回了render的最開始的方法調(diào)用,剩下的就是遞歸調(diào)用,直到找到原始的值類型的那一層,渲染到Fragment上。
__commitText :直接修改文本節(jié)點的文本 __commitNode :清空父親節(jié)點中的startNode到endNode(最開始提到的那兩個comment占位),然后把node添加進去。
當(dāng)遞歸回到最頂層后, commitNode 拿到的就是完整的 fragment ,塞到容器中就可以了。
核心流程
至此,第一次的渲染完成,大致流程如下
可能聽起來有些繞,我們可以暫時忽略 Template ,它是一個中間狀態(tài)
TemplateResult 是類似 JSX 的一種更輕量的對于模版的字面量描述,是一個模型 TemplateInstance 可以理解成一個小的 MVC 框架的嵌套
DOM(fragment) 是應(yīng)用的外層框架,是不變的 View 部分 TemplateResult 中的成員 value 是 Model Controller(Part)中連接了 View 和 Model。提供了更新數(shù)據(jù)的方法(setValue)和渲染到視圖的方法(Commit)
更新渲染
可以類比SQL執(zhí)行過程中的庫緩存,如果SQL結(jié)構(gòu)一致就復(fù)用已有的模型 逐層比較檢查所有的緩存是否命中(對比類型 和 模版 - strings結(jié)構(gòu))
如果命中的話就使用已有模版,找到 TemplateInstance 的 Part ,把 templateResult 的 value 更新給 Part 如果沒有命中的話,就走第一次渲染的流程
效率
帶標(biāo)簽的模版字符串 執(zhí)行相比 JSX 會更加高效。 JSX 的每次 render 都需要完整的構(gòu)造一個虛擬DOM,而 lit-html ,則只是重新構(gòu)建一個十分輕量的 TemplateResult 對象,變化的只有 value 集合。 從 TemplateResult 到 < template> 的過程,是直接從 TemplateResult 構(gòu)造 html ,然后使用 template.innerHTML 完成解析的。這個過程完全使用瀏覽器自己的語法解析器來完成的。由于使用了 template 技術(shù),這個DOM是一個Fragement,并不是真實DOM的一部分,內(nèi)存占用小 實際渲染的DOM生成,是從 template.importNode 來完成DOM的復(fù)制。而不是像React一樣逐個Dom節(jié)點的創(chuàng)建。對于較大的DOM,效率十分可觀 在增量更新的過程中,Lit 和 React 相類似,都是按照相同層次的節(jié)點重用的方式,React通過 diff(VDOM, DOM) 來實現(xiàn)增量更新,而LitHtml并沒有使用diff算法,而是基于相同模板的渲染,只需要對動態(tài)部分進行更新即可。沒有diff算法會更加的輕
有關(guān)注過尤大狀態(tài)的同學(xué)應(yīng)該在Vue 3 發(fā)布的時候,可能會看到過一個東西橫空出世,vue-lit,vue-lit就是基于lit-html模版引擎和@vue/reactivity的數(shù)據(jù)綁定做的一款面向未來的玩具。 Lit 自身也提供了一個數(shù)據(jù)綁定,數(shù)據(jù)響應(yīng)式的包來支撐整個框架
Lit-element
OK,模版語法有了,剩下的就是如何把狀態(tài)變化響應(yīng)式的應(yīng)用到模版里了。
如何使用
這部分實際上不復(fù)雜,有過Vue開發(fā)經(jīng)歷的同學(xué)一定都清楚Vue是如何將數(shù)據(jù)和視圖綁定起來。 Lit-element 也是如此 在Lit中,你需要這樣聲明一個組件
scala
代碼解讀
復(fù)制代碼
@customElement('simple-greeting') export class SimpleGreeting extends LitElement { /* ..*/ }
customElement 實際上是 customElement.defined 的語法糖,而 LitElement 是 Lit 提供的一個基類,其中就處理了數(shù)據(jù)的響應(yīng)式處理(實際上 LitElement 還繼承了 UpdateElement ,由 UpdateElement 做響應(yīng)式的處理)。
Reactivity Property
我們先看看,Lit的文檔中要求怎么定義 reactivity property
scala
代碼解讀
復(fù)制代碼
class MyElement extends LitElement { @property() name: string; }
我們會會發(fā)現(xiàn),如果需要響應(yīng)式屬性的話,需要使用 property 這個裝飾器來裝飾屬性,property 這個裝飾器的邏輯為,調(diào)用所在類的靜態(tài)方法 createProperty 往類的靜態(tài)成員 _classProperties 中注冊這個屬性,同時,給這個屬性添加 getter 和 setter,到這里,類準(zhǔn)備工作就做好了。
getter:直接取 setter:更新后觸發(fā)更新
每次在組件內(nèi)部修改 reactive property 的時候,屬性更新完成后會重新調(diào)用 lit-html 的 render 方法渲染到UI上。
這個和 state 的概念十分的相似,那么 lit-element 又是如何處理外部傳輸屬性(props)的變化呢? 這里我們需要應(yīng)用到前面提到的 Web-Component 的生命周期get observedAttributes 和 attributeChangedCallback 。每次當(dāng)傳遞給 component 的屬性發(fā)生變化的時候,這兩個周期就會觸發(fā),只需要查詢是否在 _classProperties 中,并主動更新 reactive property 即可。 除此之外,property 裝飾器還可以接受一個 options 配置一些屬性來進行適配
attribute - 定義這個成員變量是否和元素屬性綁定 converter - 定義轉(zhuǎn)化邏輯,從元素屬性(都是string)到真實屬性 hasChanged - 判斷屬性是否發(fā)生變化 type - 在沒有定義converter時使用,轉(zhuǎn)化元素類型 state - 如果定義了state的話,象征這個成員變量是一個內(nèi)部狀態(tài),是私有的,新版的 Lit 提供了一個單獨的裝飾器@state 來替代這個屬性
Lit 剩下的諸如裝飾器,事件綁定之類的就不再展開細說了,有興趣的同學(xué)可以去閱讀下源碼,整體上比 React 易讀性高的多(Doge)。至此,一個完整的可用的面向未來的前端框架就完成了。
小結(jié)
Lit 因為兼容性問題現(xiàn)在還不能應(yīng)用到實際的業(yè)務(wù)場景中,但是確實是一個值得關(guān)注和學(xué)習(xí)的框架。其中的一些設(shè)計思想和理念,跳出了 React 限制的框架,給出了前端框架的另一種可能的解決方案。在框架領(lǐng)域上一家獨大不是什么好事,有更多的奇思妙想和擁抱未來才能讓前端的發(fā)展更加廣闊。
還有個不能實際場景應(yīng)用的問題,Lit 還在快速的迭代中,每次都是很大的 Breaking changes。比如剛才提到的UpdateElement又被拆成單獨的包了。。。。
原文鏈接:https://juejin.cn/post/6976557762377416718
柚子快報激活碼778899分享:Google的新前端框架Lit
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。