上一篇介绍了WebComponents的基本用法。今天我们就来看看lit-html,这是谷歌基于这种原生技术的二次归档框架。其实早在Google提出WebComponents的时候,就在此基础上发布了Polymer框架。只是这个框架一直雷声大雨点小,内部好像对这个项目不满意,然后他们的团队开发了两个比较现代的框架(或者库?):lit-html,lit-element,今天的文章将重点介绍lit-html的用法和优点。发展历程在讲lit-html之前,我们先来看看前端通过JavaScript操作页面。我们经历了几个阶段:在开发阶段,原生DOMAPI最早是通过DOMAPI来操作页面元素。操作步骤比较繁琐,JS引擎与浏览器浏览器DOM对象的通信也比较耗时,频繁的DOM操作对浏览器性能影响很大。var$box=document.getElementById('box')var$head=document.createElement('h1')var$content=document.createElement('div')$head.innerText='关注我的公众号'$内容.innerText='打开微信搜索:《自然醒的笔记本》'$box.append($head)$box.append($content)jQuery操作DOMjQuery的出现让DOM操作更加方便,很多横切内部做了浏览器兼容性处理,大大提升了开发体验,同时也有丰富的插件系统和详尽的文档。var$box=$('#box')var$head=$('
',{text:'关注我的公众号'})var$content=$('
',{text:'打开微信搜索:“自然觉醒笔记本”'})$box.append($head,$content)虽然提供了方便的操作,但是因为里面有很多兼容代码,所以性能大大降低了。而且它的链式调用让开发者写的面条代码经常被诟病(PS,个人觉得这不是缺点,只是有些人看不懂)。模板操作“模板引擎”原本是后端MVC框架的View层,用于拼接生成HTML代码。例如,mustache是一组可用于多种语言的模板引擎。后来小胡子的前端框架也开始摆弄MVC模式,渐渐的前端也开始引入模板的概念,让操作页面元素变得更加简单。在以下情况下,模板通过angluar.js中的指令使用:varapp=angular.module("box",[]);app.directive("myMessage",function(){return{template:''+'
关注我的公众号
'+'
打开微信搜索:“自然觉醒笔记本”
'}})后来Vue将模板与虚拟DOM结合,进一步完善了Vue中模板的性能,但模板也有其缺陷。不管是什么模板引擎,启动时都需要花时间解析模板,这是无法避免的;连接模板和JavaScript数据比较麻烦,更新数据时需要更新模板;有各种模板创建自己的语法结构。使用不同的模板引擎,需要重新学习其语法糖,对开发体验不是很友好;JSXGitHub-OpenJSX/logo:LogoofJSX-IRReact在官方文档中是这样介绍JSX的:“JSX,是对JavaScript的语法扩展。我们建议将JSX与React结合使用。JSX是描述JSX本质的好方法一个UI应该以它应该交互的方式表现。JSX可能让人想起模板语言,但它具有JavaScript的全部功能。vartitle='关注我公众号'varcontent='打开微信搜索:“自然觉醒笔记本”'constelement=
{title}
{content}
/div>;ReactDOM.render(element,document.getElementById('root'))JSX的出现给前端开发模式带来了更多的想象空间,引入了函数式编程的思想。UI=fn(state)但这也带来了一个问题,必须对JSX语法进行转义处理成React.createElement的形式,这也增加了React的上手难度,令很多新手望而却步。lit-html简介lit-html的出现尽可能的避免了之前模板引擎存在的问题,通过现代浏览器的原生能力构建模板。ES6提供的模板字面量;
WebComponents提供的标签;//Importlit-htmlimport{html,render}from'lit-html';//Defineatetemplateconsttemplate=(title,content)=>html`${title}
${content}
`;//Renderthetemplatetothedocumentrender(template('关注我公众号','打开微信搜索:“自然醒笔记本”'),document.body);由于模板语法使用原生模板字符,无需转义即可直接使用,并且和JSX一样,也可以使用JavaScript语法进行遍历和逻辑控制。constskillTpl=(title,skills)=>html`${title||'技能列表'}
${skills.map(i=>html`- ${i}
`)}
`;render(skillTpl('我的技能',['Vue','React','Angluar']),document.body);Lit-html除了这样写方便外,也为Vue提供了类似的事件绑定方式。constInput=(defaultValue)=>html`name:{console.log(evt.target.value)}}/>`;render(Input('inputyourname'),document.body);样式绑定除了使用原生模板字符串编写模板外,lit-html还自带CSS-in-JS功能。import{html,render}from'lit-html';import{styleMap}from'lit-html/directives/style-map.js';constskillTpl=(title,skills,highlight)=>{conststyles={backgroundColor:highlight?'yellow':'',};returnhtml`${title||'技能列表'}
${skills.map(i=>html`${i}`)}`};render(skillTpl('我的技能',['Vue','React','Angluar'],true),document.body);渲染过程作为一个模板引擎,lit-html的主要作用就是将模板渲染到页面上。与React、Vue等框架相比,它更侧重于渲染。让我们看一下lit-html工作流程的基础知识。//Importlit-htmlimport{html,render}from'lit-html';//DefineatetemplateconstmyTemplate=(name)=>html`Hello${name}
`;//Renderthetemplatetothedocumentrender(myTemplate('World'),document.body);从前面的案例可以看出,lit-html常用的两个外部API是html和render。构造模板html是标签函数,属于ES6新语法。如果不记得label函数的用法,可以打开Mozilla文档(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals)复习。exportconsthtml=(字符串,...值)=>{...};html标签函数会接受多个参数,第一个参数是由静态字符串组成的数组,后面的参数是动态传入的表达式。我们可以写个例子看看传入html标签函数的参数是什么样子的:constfoo='吴彦祖';constbar='梁朝伟';html`你好${foo},我是${bar
`;整个字符串会被动态表达式切割成三部分,三部分组成一个数组,作为第一个参数传递给html标签函数,动态表达式计算得到的值传递给在一次作为以下参数。我们可以把字符串和值打印出来看看:loglit-html会把这两个参数传给TemplateResult进行实例化。exportconsthtml=(strings,...values)=>{returnnewTemplateResult(strings,values);};constmarker=`{{lit-${String(Math.random()).slice(2)}}}`;constnodeMarker=``;exportclassTemplateResult{constructor(strings,values){this.strings=strings;this.values=values;}getHTML(){constl=this.strings.length-1;lethtml='';letisCommentBinding=false;for(leti=0;i{//先获取DOM节点前对应的缓存letpart=parts.get(container);//如果没有缓存,则重新创建if(part===undefined){part=newNodePart()parts.set(container,part);part.appendInto(container);}//设置TemplateResult为partpart.setValue(result);//调用commit创建或更新part.commit();};在render阶段,会先去parts中寻找之前构建好的partcache。Part可以理解为节点构造器,用于将模板的内容渲染成真实的DOM节点。如果part缓存不存在,则先构造一个,然后调用appendInto方法,在DOM节点前后分别插入两个注释节点,用于后续插入到模板中。constcreateMarker=()=>document.createComment('');exportclassNodePart{appendInto(container){this.startNode=container.appendChild(createMarker());this.endNode=container.appendChild(createMarker());}}然后真实节点由commit方法创建,插入到两个注释节点中。下面看一下commit方法的具体操作:exportclassNodePart{setValue(result){//将templateResult放入__pendingValue属性this.__pendingValue=result;}commit(){constvalue=this.__pendingValue;//根据value不同类型的操作if(valueinstanceofTemplateResult){//通过html标签方法获取的value//必须是TemplateResult类型的this.__commitTemplateResult(value);}else{this.__commitText(value);}}__commitTemplateResult(value){//调用templateFactory构造模板节点consttemplate=templateFactory(value);//如果模板已经构建过一次,则更新模板if(this.value.template===template){//console.log('UpdateDOM',value)this.value.update(value.values);}else{//通过模板节点构造模板实例constinstance=newTemplateInstance(template);//将templateResult中的值更新为模板实例constfragment=instance._clone();instance.update(value.values);//复制th中的DOM节点etemplate并插入到页面中this.__commitNode(fragment);//将模板实例缓存到value属性中,用于后续判断是否为更新操作this.value=instance;}}}实例化后的模板会先调用instance._clone()执行复制操作,然后通过instance.update(value.values)将计算出的动态表达式插入其中。最后调用__commitNode将复制模板得到的节点插入到真实的DOM中。exportclassNodePart{__insert(node){this.endNode.parentNode.insertBefore(node,this.endNode);}__commitNode(value){this.__insert(value);this.value=value;}}可以看到lit-html还有没有像Vue和React那样将模板或JSX构造成虚拟DOM的过程。它只提供了一个轻量级的html标签方法,将模板字符转换成TemplateResult,然后使用注释节点填充动态位置。TemplateResult最终是通过创建标签来创建的,然后通过浏览器内置的innerHTML来解析模板。这个过程也很轻量级,相当于把所有可以交给浏览器的部分都交给了浏览器,包括模板创建之后的节点。复制操作。exportclassTemplateInstance{_clone(){const{element}=this.template;constfragment=document.importNode(element.content,true);//省略一些操作...returnfragment;}}其他的lit-html只是一个高效的模板引擎,如果你想用它来编写业务代码,它仍然缺乏Vue和React提供的生命周期和数据绑定能力。为了完成这部分能力,Polymer项目组还提供了另一个框架:lit-element,可以用来创建WebComponents。除了官方的lit-element框架,Vue的作者还剥离了Vue的响应式部分,并与lit-html结合创建了一个vue-lit(https://github.com/yyx990803/vue-lit)框架,一共写了70行代码,有兴趣的可以看看。