前言:本文围绕virtual-dom展开,vue/react借助VirtualDOM带来分层设计无论是.vue文件还是jsx文件,virtual-dom用来描述实际的dom结构,两者都有render的实现过程。什么是渲染器以及如何实现多端渲染DOM工具(一个函数,通常称为render)带来的可能性,渲染器的工作流程分为mount和patch两个阶段,如果老的VNode存在,会使用新的VNode与旧的VNode进行比较,尽量减少Resource开销来完成DOM的更新,这个过程称为patch,或“打补丁”。如果旧的VNode不存在,则新的VNode将作为新的DOM直接挂载。这个过程称为挂载。1.1:渲染器需要根据实际框架(vue/react)用js对象描述文档结构。不管是.vue文件还是jsx文件,都离我们在控制台看到的实际dom结构还有一定的距离。当然,具体的框架会进行相应的解析。当然,在web层面,就是我们熟悉的DOM文档结构。简化帧解析过程。我们的目标是实现从实际的dom结构到存储在内存中,由js对象描述的virtual-domdom结构,可以抽象成一个树状的数据结构,真正打印出dom结构。模拟从virtual-dom到实际dom结构的过程1.1.1:aExampleofabstractvirtual-dom//一个ul-li列表可以表示如下Item1Item2li>Item3//树数据varelement={tagName:'ul',//节点标签名props:{//DOM属性,使用一个对象存储键值对id:'list'},children:[//该节点的子节点{tagName:'li',props:{class:'item'},children:["Item1"]},{tagName:'li',props:{class:'item'},children:["Item2"]},{tagName:'li',props:{class:'item'},children:["Item3"]},]}这里的元素是virtual-dom的样子,但实际上是从最外层的label开始的,结构比这更复杂!1.1.2:BFS/DFS遍历dom结构为了验证dom文档结构可以抽象成上面的javascript对象,可以实际遍历dom结构。以DFS为例,遍历树结构,打印当前页面的tagName、classList、levelconstDFS=function(node){if(!node){return}letdeep=arguments[1]||1console.log(`${node.nodeName}.${node.classList}${deep}`)如果(!node.children.length){return}Array.from(node.children).forEach((item)=>DFS(item,deep+1))}//在body标签中添加了testid属性varaimNode=document.getElementById('test')DFS(aimNode)1.1.3:从virtual-dom到realdom结构Vue的render方法是实例的私有方法,用于将实例渲染成虚拟Node,即virtual-dom,体验相同作为这里render的区别决定了基本的vNode类,functionVnode(tagName,props,children){this.tagName=tagNamethis.props=propsthis.children=children}//添加render方法Vnode.prototype.render=function(){varel=document.createElement(this.tagName)//根据tagName构建varprops=this.propsfor(varpropNameinprops){//设置节点的DOM属性varpropValue=props[propName]el.setAttribute(propName,propValue)}varchildren=this.children||[]children.forEach(function(child){varchildEl=(childinstanceofVnode)?child.render()//如果子节点也是虚拟DOM,则递归构建DOM节点:document.createTextNode(child)//如果是字符串,只建一个文本节点el.appendChild(childEl)})returnel}//实例化ul,它是一个virtual-dom对象varul=newVnode('ul',{id:'list'},[newVnode('li',{class:'item'},['Item1']),newVnode('li',{class:'item''},['Item2']),newVnode('li',{class:'item'},['Item3'])])//挂载到bodyvarulRoot=ul.render()document.body.appendChild(ulRoot)2:渲染过程2.1:virtual-dom参与了哪些进程functionrender(vnode,container){//getvnodeconstprevVNode=container.vnodeif(prevVNode==null){if(vnode){//没有旧的VNode,只有新的VNode使用`mount`函数挂载新的VNodemount(vnode,container)//将新的VNode添加到container.vnode属性中,这样旧的VNode就会挂载是下一个渲染Thereexistscontainer.vnode=vnode}}else{if(vnode){//有旧的VNodes和新的VNodes。然后调用`patch`函数打补丁patch(prevVNode,vnode,container)//更新container.vnodecontainer.vnode=vnode}else{//有一个旧的VNode但没有新的VNode,这意味着DOM应该是删除。removeChild函数在浏览器中可用。container.removeChild(prevVNode.el)container.vnode=null}}}2.1.1:mountmount模拟从vnode到实际dom的过程,见前面的render方法2.1.2:updatepatchvarpatches=diff(tree,newTree)这里需要介绍diff算法分析snabbdom的源码,手把手教大家实现一个精简的VirtualDOM库。不同框架的实现是不同的。v3.0更新了diff算法patch(root,patches),对之前的dom结构打补丁,实现差异化更新。3:多端渲染3.1:rendercannotgotodom前面的例子是将VirtualDOM渲染成web平台的真实DOM。由于是面向浏览器的,渲染器需要调用浏览器提供的DOM编程接口document.createElementel.appendChilddocument.body。appendChild为实现多端渲染,render方法不需要过多依赖DOM编程接口。对应操作节点的接口由具体平台暴露出来,满足类似dom节点的节点增删改查:可以理解为对应平台的展示单元,比如web端展示的是domfunctionspecialRenderer(options){const{hanlde:{createElement:platformCreateElement,appendChild:platformAppendChild,insertBefore:platformInsertBefore,removeChild:platformRemoveChild,parentNode:platformParentNode,nextSibling:platformNextSibling,querySelector:platformQuerySelector}}=options}Vue3提供了一个名为@vue/runtime-test的包用于方便开发者在无DOM环境中测试组件的渲染内容。3.2:Taro多端实现猜想Taro官方文档Taro是一套遵循React语法规范的多端开发解决方案。现在市面上的终端形式多种多样,流行的有Web、React-Native、微信小程序等各种终端。当业务需求同时需要不同端的性能时,为不同端编写多套代码。成本显然非常高。这个时候,只用一套代码就可以适配多种终端的能力就显得极为必要了。使用Taro,我们可以只写一套代码,然后使用Taro的编译工具分别编译源码,可以在不同的终端使用(微信/百度/支付宝/字节跳动/QQ小程序、快应用、H5、React-本机等)来运行代码。Taro的多端实现推测,根据Taro的UI描述,dom结构仍然会使用virtual-dom描述来实现web和多个小程序的编译和执行。应该在render阶段判断具体的环境,提供dom(platformelements)类似的操作。现在回头看看React的LearnOnce,WriteAnywhereslogan,其实是在强调其支持各种渲染层框架的原理,实现的结果大相径庭。讨论:基于react的taro如何实现一套代码多终端运行?前面的讨论其实也不是没有意义,只是render的立足点不同而已。基于UI层面的描述是可以实现的,但是没有考虑到的问题包括Framewrok——通俗的说,完成一个app交互任务所需要的规范,比如生命周期(onLoad、onShow)、模块化和数据管理,etc.library-可以理解为要添加“方法封装集合”。..3.3.1标准语言统一业务代码统一约束,借助babel输出多端代码3.3.2:Framework/Library处理行业处理思路不同框架使其生态易于使用,让很多组件可以直接使用。(folk?)可以使用babel-babel/polyfill基于语法和api处理来理解taro:我们选择了微信小程序的组件库和API作为Taro的运行标准4:总结再回顾一下这张图,基本思路是借助带来分层设计的VirtualDOM,每一步的单独处理都可以自成体系。框架多端编译的概念层出不穷。站在一个开发者的角度,知道背后到底做了哪些改变,就可以根据自己的兴趣选择方向。本文对一些方法和操作进行了简化,目的是整理流程,知识点包括但不限于:将Dom结构描述为js对象--生成virtual-dom并从js中生成实际的dom结构object--renderrenderer会更新JS对象与之前的diff比较——实现diff算法比较结果并将patch应用到实际的dom树上更新Dom结构——differenceupdate实现自定义render函数到连接到多个平台。目前实现多端渲染的思路是5:参考阅读VirtualDOM和基础DFS:https://zhuanlan.zhihu.com/p/64187708Babel-runtime使用与性能优化Renderer解读:http:///hcysun.me/vue-design/zh/renderer-advanced.html跨端框架架构解读:https://segmentfault.com/a/1190000018307526框架和库的区别:https://zhuanlan.zhihu.com/p/26078359