<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>

          下一代的模板引擎:lit-html

          共 8212字,需瀏覽 17分鐘

           ·

          2021-04-03 04:27

          前面的文章介紹了 Web Components 的基本用法,今天來看看基于這個(gè)原生技術(shù),Google 二次封存的框架 lit-html。

          其實(shí)早在 Google 提出 Web Components 的時(shí)候,就在此基礎(chǔ)上發(fā)布了 Polymer 框架。只是這個(gè)框架一直雷聲大雨點(diǎn)小,內(nèi)部似乎也對這個(gè)項(xiàng)目不太滿意,然后他們團(tuán)隊(duì)又開發(fā)了兩個(gè)更加現(xiàn)代化的框架(或者說是庫?):lit-html、lit-element,今天的文章會(huì)重點(diǎn)介紹 ?lit-html 的用法以及優(yōu)勢。

          287cbb5ff2130d7fc7ff1eb85447376e.webp

          發(fā)展歷程

          在講到 lit-html 之前,我們先看看前端通過 JavaScript 操作頁面,經(jīng)歷過的幾個(gè)階段:

          de6de4a7c02c8214ce8e4c112c373bb7.webp發(fā)展階段

          原生 DOM API

          最早通過 DOM API 操作頁面元素,操作步驟較為繁瑣,而且 JS 引擎與瀏覽器 DOM 對象的通信相對耗時(shí),頻繁的 DOM 操作對瀏覽器性能影響較大。

          var?$box?=?document.getElementById('box')
          var?$head?=?document.createElement('h1')
          var?$content?=?document.createElement('div')
          $head.innerText?=?'關(guān)注我的公眾號'
          $content.innerText?=?'打開微信搜索:『自然醒的筆記本』'
          $box.append($head)
          $box.append($content)
          6a7e37cb5f9dcd4a3d88ef09ede31b7a.webp

          jQuery 操作 DOM

          jQuery 的出現(xiàn),讓 DOM 操作更加便捷,內(nèi)部還做了很多跨瀏覽器的兼容性處理,極大的提升了開發(fā)體驗(yàn),并且還擁有豐富的插件體系和詳細(xì)的文檔。

          600dcc4de8b4d75534878ddf8ff7ba3c.webp
          var?$box?=?$('#box')

          var?$head?=?$('<h1/>',?{?text:?'關(guān)注我的公眾號'?})
          var?$content?=?$('<div/>',?{?text:?'打開微信搜索:『自然醒的筆記本』'?})

          $box.append($head,?$content)
          de4508d0cbac8bb2a4f9afae3164cc0a.webp

          雖然提供了便捷的操作,由于其內(nèi)部有很多兼容性代碼,在性能上就大打折扣了。而且它的鏈?zhǔn)秸{(diào)用,讓開發(fā)者寫出的面條式代碼也經(jīng)常讓人詬?。≒S. 個(gè)人認(rèn)為這也不能算缺點(diǎn),只是有些人看不慣罷了)。

          模板操作

          『模板引擎』最早是后端 MVC 框架的 View 層,用來拼接生成 HTML 代碼用的。比如,mustache 是一個(gè)可以用于多個(gè)語言的一套模板引擎。

          06420ba1ec34268d9b2f8a1149086ca3.webpmustache

          后來前端框架也開始搗鼓 MVC 模式,漸漸的前端也開始引入了模板的概念,讓操作頁面元素變得更加順手。下面的案例,是 angluar.js 中通過指令來使用模板:

          var?app?=?angular.module("box",?[]);

          app.directive("myMessage",?function?(){
          ??return?{
          ????template?:?''?+
          ????'<h1>關(guān)注我的公眾號</h1>'?+
          ????'<div>打開微信搜索:『自然醒的筆記本』</div>'
          ??}
          })
          8fdeca295d2cd4444c08301b9cd27651.webp

          后來的 Vue 更是將模板與虛擬 DOM 進(jìn)行了結(jié)合,更進(jìn)一步的提升了 Vue 中模板的性能,但是模板也有其缺陷存在。

          • 不管是什么模板引擎,在啟動(dòng)時(shí),解析模板是需要花時(shí)間,這是沒有辦法避免的;
          • 連接模板與 JavaScript 的數(shù)據(jù)比較麻煩,而且在數(shù)據(jù)更新時(shí)還需進(jìn)行模板的更新;
          • 各式各樣的模板創(chuàng)造了自己的語法結(jié)構(gòu),使用不同的模板引擎,就需要重新學(xué)習(xí)一遍其語法糖,這對開發(fā)體驗(yàn)不是很友好;

          JSX

          e7f99219d9eadc274596c434b0d299b8.webpGitHub - OpenJSX/logo: Logo of JSX-IR

          React 在官方文檔中這樣介紹 JSX:

          JSX,是一個(gè) JavaScript 的語法擴(kuò)展。我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應(yīng)該呈現(xiàn)出它應(yīng)有交互的本質(zhì)形式。JSX 可能會(huì)使人聯(lián)想到模板語言,但它具有 JavaScript 的全部功能。

          var?title?=?'關(guān)注我的公眾號'
          var?content?=?'打開微信搜索:『自然醒的筆記本』'

          const?element?=?<div>
          ??<h1>{title}</h1>
          ??<div>{content}</div>
          </div>
          ;

          ReactDOM.render(
          ??element,
          ??document.getElementById('root')
          )
          e246a564e112678c6bab9cb3f1620651.webp

          JSX 的出現(xiàn),給前端的開發(fā)模式帶來更大的想象空間,更是引入了函數(shù)式編程的思想。

          UI?=?fn(state)

          但是這也帶來了一個(gè)問題,JSX 語法必須經(jīng)過轉(zhuǎn)義,將其處理成 React.createElement 的形式,這也提高了 React 的上手難度,很多新手望而卻步。

          lit-html 介紹

          lit-html 的出現(xiàn)就盡可能的規(guī)避了之前模板引擎的問題,通過現(xiàn)代瀏覽器原生的能力來構(gòu)建模板。

          • ES6 提供的模板字面量;
          • Web Components 提供的 <template> 標(biāo)簽;
          //?Import?lit-html
          import?{html,?render}?from?'lit-html';

          //?Define?a?template
          const?template?=?(title,?content)?=>?html`
          ??<h1>
          ${title}</h1>
          ??<div>
          ${content}</div>
          `
          ;

          //?Render?the?template?to?the?document
          render(
          ??template('關(guān)注我的公眾號',?'打開微信搜索:『自然醒的筆記本』'),
          ??document.body
          );
          d32730fbad0c52878f6eecb33102c55a.webp

          模板語法

          由于使用了原生的模板字符,可以無需轉(zhuǎn)義,直接進(jìn)行使用,而且和 JSX 一樣也能使用 JavaScript 語法進(jìn)行遍歷和邏輯控制。

          const?skillTpl?=?(title,?skills)?=>?html`
          ??<h2>
          ${title?||?'技能列表'?}</h2>
          ??<ul>
          ????
          ${skills.map(i?=>?html`<li>${i}</li>`)}
          ??</ul>
          `
          ;

          render(
          ??skillTpl('我的技能',?['Vue',?'React',?'Angluar']),
          ??document.body
          );
          cad2e38ddeed95a4b5f979104b312932.webp

          除了這種寫法上的便利,lit-html 內(nèi)部也提供了Vue 類似的事件綁定方式。

          const?Input?=?(defaultValue)?=>?html`
          ??name:?<input?value=
          ${defaultValue}?@input=${(evt)?=>?{
          ????console.log(evt.target.value)
          ??}
          }?/>
          `
          ;

          render(
          ??Input('input?your?name'),
          ??document.body
          );
          9e8efc117ad073214a9188c1cd638e7b.webp

          樣式的綁定

          除了使用原生模板字符串編寫模板外,lit-html 天生自帶的 CSS-in-JS 的能力。

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

          const?skillTpl?=?(title,?skills,?highlight)?=>?{
          ?const?styles?=?{
          ???backgroundColor:?highlight???'yellow'?:?'',
          ?};
          ?return?html`
          ???<h2>
          ${title?||?'技能列表'?}</h2>
          ???<ul?style=
          ${styleMap(styles)}>
          ?????
          ${skills.map(i?=>?html`<li>${i}</li>`)}
          ???</ul>
          ?`

          };

          render(
          ?skillTpl('我的技能',?['Vue',?'React',?'Angluar'],?true),
          ?document.body
          );
          fb0838c5911b49018e0aaebb68cb34d5.webp

          渲染流程

          做為一個(gè)模板引擎,lit-html 的主要作用就是將模板渲染到頁面上,相比起 React、Vue 等框架,它更加專注于渲染,下面我們看看 lit-html 的基本工作流程。

          //?Import?lit-html
          import?{?html,?render?}?from?'lit-html';

          //?Define?a?template
          const?myTemplate?=?(name)?=>?html`<p>Hello?${name}</p>`;

          //?Render?the?template?to?the?document
          render(myTemplate('World'),?document.body);

          通過前面的案例也能看出,lit-html 對外常用的兩個(gè) api 是 html 和 render。

          構(gòu)造模板

          html 是一個(gè)標(biāo)簽函數(shù),屬于 ES6 新增語法,如果不記得標(biāo)簽函數(shù)的用法,可以打開 Mozilla 的文檔(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals)復(fù)習(xí)下。

          export?const?html?=?(strings,?...values)?=>?{
          ??……
          };

          html 標(biāo)簽函數(shù)會(huì)接受多個(gè)參數(shù),第一個(gè)參數(shù)為靜態(tài)字符串組成的數(shù)組,后面的參數(shù)為動(dòng)態(tài)傳入的表達(dá)式。我們可以寫一個(gè)案例,看看傳入的 html 標(biāo)簽函數(shù)的參數(shù)到底長什么樣:

          const?foo?=?'吳彥祖';
          const?bar?=?'梁朝偉';

          html`<p>Hello?${foo},?I'm?${bar}</p>`;
          06988669f43166a20d8485af7b902594.webp

          整個(gè)字符串會(huì)被動(dòng)態(tài)的表達(dá)式進(jìn)行切割成三部分,這個(gè)三個(gè)部分會(huì)組成一個(gè)數(shù)組,做為第一個(gè)參數(shù)傳入 html 標(biāo)簽函數(shù),而動(dòng)態(tài)的表達(dá)式經(jīng)過計(jì)算后得到的值會(huì)做為后面的參數(shù)一次傳入,我們可以將 strings 和 values 打印出來看看:

          26c2bfd3f15e3a5142534ce5f0efc65f.webplog

          lit-html 會(huì)將這兩個(gè)參數(shù)傳入 TemplateResult 中,進(jìn)行實(shí)例化操作。

          export?const?html?=?(strings,?...values)?=>?{
          ??return?new?TemplateResult(strings,?values);
          };
          //?生成一個(gè)隨機(jī)字符
          const?marker?=?`{{lit-${String(Math.random()).slice(2)}}}`;
          const?nodeMarker?=?`<!--${marker}-->`;

          export?class?TemplateResult?{
          ?constructor(strings,?values)?{
          ??this.strings?=?strings;
          ??this.values?=?values;
          ?}
          ?getHTML()?{
          ??const?l?=?this.strings.length?-?1;
          ??let?html?=?'';
          ??let?isCommentBinding?=?false;
          ??for?(let?i?=?0;?i?<?l;?i++)?{
          ???const?s?=?this.strings[i];
          ???html?+=?s?+?nodeMarker;
          ??}
          ??html?+=?this.strings[l];
          ??return?html;
          ?}
          ?getTemplateElement()?{
          ??const?template?=?document.createElement('template');
          ??let?value?=?this.getHTML();
          ??template.innerHTML?=?value;
          ??return?template;
          ?}
          }

          實(shí)例化的 ?TemplateResult 會(huì)提供一個(gè) getTemplateElement 方法,該方法會(huì)創(chuàng)建一個(gè) template 標(biāo)簽,然后會(huì)將 getHTML 的值傳入 template 標(biāo)簽的 innerHTML 中。而 getHTML 方法的作用,就是在之前傳入的靜態(tài)字符串中間插入 HTML 注釋。前面的案例中,如果調(diào)用 getHTML 得到的結(jié)果如下。

          4e08c1c1b4b3b9925664a9dcee5702b6.webp

          渲染到頁面

          render 方法會(huì)接受兩個(gè)參數(shù),第一個(gè)參數(shù)為 html 標(biāo)簽函數(shù)返回的 TemplateResult,第二個(gè)參數(shù)為一個(gè)真實(shí)的 DOM 節(jié)點(diǎn)。

          export?const?parts?=?new?WeakMap();
          export?const?render?=?(result,?container)?=>?{
          ??//?先獲取DOM節(jié)點(diǎn)之前對應(yīng)的緩存
          ??let?part?=?parts.get(container);
          ??//?如果不存在緩存,則重新創(chuàng)建
          ??if?(part?===?undefined)?{
          ????part?=?new?NodePart()
          ????parts.set(container,?part);
          ????part.appendInto(container);
          ??}
          ??//?將?TemplateResult?設(shè)置到?part?中
          ??part.setValue(result);
          ??//?調(diào)用?commit?進(jìn)行節(jié)點(diǎn)的創(chuàng)建或更新
          ??part.commit();
          };

          render 階段會(huì)先到 parts 里面查找之前構(gòu)造過的 part 緩存??梢詫?part 理解為一個(gè)節(jié)點(diǎn)的構(gòu)造器,用來將 template 的內(nèi)容渲染到真實(shí)的 DOM 節(jié)點(diǎn)中。

          如果 part 緩存不存在,會(huì)先構(gòu)造一個(gè),然后調(diào)用 appendInto 方法,該方法會(huì)在 DOM 節(jié)點(diǎn)的前后插入兩個(gè)注釋節(jié)點(diǎn),用于后續(xù)插入模板。

          const?createMarker?=?()?=>?document.createComment('');
          export?class?NodePart?{
          ??appendInto(container)?{
          ????this.startNode?=?container.appendChild(createMarker());
          ????this.endNode?=?container.appendChild(createMarker());
          ??}
          }
          7240a402d0aef770cd2b27303413429d.webp

          然后通過 commit 方法創(chuàng)建真實(shí)的節(jié)點(diǎn),并插入到兩個(gè)注釋節(jié)點(diǎn)中。下面我們看看 commit 方法的具體操作:

          export?class?NodePart?{
          ??setValue(result)?{
          ????//?將?templateResult?放入?__pendingValue?屬性中
          ????this.__pendingValue?=?result;
          ??}
          ??commit()?{
          ????const?value?=?this.__pendingValue;
          ????//?依據(jù)?value?的不同類型進(jìn)行不同的操作
          ????if?(value?instanceof?TemplateResult)?{
          ??????//?通過?html?標(biāo)簽方法得到的?value
          ??????//?肯定是?TemplateResult?類型的
          ??????this.__commitTemplateResult(value);
          ????}?else?{
          ??????this.__commitText(value);
          ????}
          ??}
          ??__commitTemplateResult(value)?{
          ????//?調(diào)用?templateFactory?構(gòu)造模板節(jié)點(diǎn)
          ????const?template?=?templateFactory(value);
          ????//?如果之前已經(jīng)構(gòu)建過一次模板,則進(jìn)行更新
          ????if?(this.value.template?===?template)?{
          ??????//?console.log('更新DOM',?value)
          ??????this.value.update(value.values);
          ????}?else?{
          ??????//?通過模板節(jié)點(diǎn)構(gòu)造模板實(shí)例
          ??????const?instance?=?new?TemplateInstance(template);
          ??????//?將?templateResult?中的?values?更新到模板實(shí)例中
          ???const?fragment?=?instance._clone();
          ??????instance.update(value.values);
          ??????//?拷貝模板中的?DOM?節(jié)點(diǎn),插入到頁面
          ??????this.__commitNode(fragment);
          ??????//?模板實(shí)例放入?value?屬性進(jìn)行緩存,用于后續(xù)判斷是否是更新操作
          ??????this.value?=?instance;
          ????}
          ??}
          }

          實(shí)例化之后的模板,首先會(huì)調(diào)用 instance._clone() 進(jìn)行一次拷貝操作,然后通過 instance.update(value.values) 將計(jì)算后的動(dòng)態(tài)表達(dá)式插入其中。

          9ca0656994683613298de2c16f8f3a52.webp

          最后調(diào)用 __commitNode 將拷貝模板得到的節(jié)點(diǎn)插入真實(shí)的 DOM 中。

          export?class?NodePart?{
          ??__insert(node)?{
          ????this.endNode.parentNode.insertBefore(node,?this.endNode);
          ??}
          ??__commitNode(value)?{
          ????this.__insert(value);
          ????this.value?=?value;
          ??}
          }
          e7250ba91fd9e24db37efa4a2330c37f.webp

          可以看到 lit-html 并沒有類似 Vue、React 那種將模板或 JSX 構(gòu)造成虛擬 DOM 的流程,只提供了一個(gè)輕量的 html 標(biāo)簽方法,將模板字符轉(zhuǎn)化為 TemplateResult,然后用注釋節(jié)點(diǎn)去填充動(dòng)態(tài)的位置。TemplateResult 最終也是通過創(chuàng)建 <template> 標(biāo)簽,然后通過瀏覽器內(nèi)置的 innerHTML 進(jìn)行模板解析的,這個(gè)過程也是十分輕量,相當(dāng)于能交給瀏覽器的部分全部交給瀏覽器來完成,包括模板創(chuàng)建完后的節(jié)點(diǎn)拷貝操作。

          export?class?TemplateInstance?{
          ??_clone()?{
          ????const?{?element?}?=?this.template;
          ????const?fragment?=?document.importNode(element.content,?true);
          ????//?省略部分操作……
          ????return?fragment;
          ??}
          }

          其他

          lit-html 只是一個(gè)高效的模板引擎,如果要用來編寫業(yè)務(wù)代碼還缺少了類似 Vue、React 提供的生命周期、數(shù)據(jù)綁定等能力。為了完成這部分的能力,Polymer 項(xiàng)目組還提供了另一個(gè)框架:lit-element,可以用來創(chuàng)建 WebComponents。

          除了官方的 lit-element 框架,Vue 的作者還將 Vue 的響應(yīng)式部分剝離,與 lit-html 進(jìn)行了結(jié)合,創(chuàng)建了一個(gè) vue-lit(https://github.com/yyx990803/vue-lit) 的框架,一共也就寫了 70 行代碼,感興趣可以看看。

          c67e048b5221b45c815ca63abd75c298.webp


          瀏覽 78
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  国产免费操逼 | 亚洲AV天天射 | 中文无码免费一区二区三区 | 先锋影音亚洲AV每日资源网站 | 成人免费视频久久久 |