petite-vue源码分析——v-if和v-for的工作原理
人肉单步调试:调用createApp根据入参生成全局作用域rootScope,创建根上下文rootCtx;调用mount为
根块构建根块对象,并以此为模板进行解析处理;解析时识别v-scope属性,根据全局作用域rootScope获取局部作用域scope。子节点的解析和渲染;获取$template属性值并生成HTML元素;子节点的深度优先遍历和解析(调用walkChildren);parsing
OFFLINEParsingOFFLINE本书接上篇第一章,我们继续人肉单步调试:识别元素带上v-if属性,调用_if原始指令分析元素和兄弟元素;将带有v-if的元素和后面带有v-else-if和v-else的元素转换为逻辑分支记录;loop遍历分支,为逻辑运算结果为真的分支创建块对象并销毁原分支的块对象(没有原分支的块对象第一次渲染),将渲染任务提交给异步队列//文件./src/walk.ts//为了便于理解,我简化了代码exportconstwalk=(node:Node,ctx:Context):ChildNode|空|void{consttype=node.nodeTypeif(type==1){//节点对于元素类型constel=nodeasElementletexp:string|nullif((exp=checkAttr(el,'v-if'))){return_if(el,exp,ctx)//返回没有`v-else-if`或`v-else`兄弟节点的最新一个}}}//文件./src/directives/if.tsinterface分支{exp?:string|null//该分支的逻辑运算表达式el:Element//该分支对应的模板元素,每次渲染时都会以该元素为模板,通过cloneNode复制一个实例到DOM树中}exportconst_if=(el:元素,exp:字符串,ctx:上下文)=>{constparent=el.parentElement!/*锚元素,由于v-if、v-else-if和v-else标识的元素在某个状态下可能不在DOM树上,*所以插入点由锚元素位置信息标记,当状态改变时,目标元素可以被插入到正确的位置。*/constanchor=newComment('v-if')parent.insertBefore(anchor,el)//逻辑分支,将v-if标识的元素作为第一个分支constbranches:Branch[]=[{exp,el}]/*定位v-else-if和v-else元素并将它们压入逻辑分支*无法控制v-else-if和v-else出现的顺序,所以我们可以这样写**但是效果是变为,最后一个分支永远没有机会匹配。*/letelseEl:元素|nullletelseExp:字符串|nullwhile((elseEl=el.nextElementSibling)){elseExp=nullif(checkAttr(elseEl,'v-else')===''||(elseExp=checkAttr(elseEl,'v-else-if'))){//从在线模板中删除分支节点parent.removeChild(elseEl)branches.push({exp:elseExp,el:elseEl})}else{break}}//保存最新的节点,不带`v-else`和`v-else-if`作为下一轮遍历分析的模板节点constnextNode=el.nextSibling//从在线模板中移除带有`v-if`的节点parent.removeChild(el)letblock:Block|undefined//当前逻辑运算结构为真的分支对象对应的块对象letactiveBranchIndex:number=-1//当前逻辑运算结构为真的分支索引//如果状态发生变化如果分支的索引逻辑操作结构为真变化,需要销毁原分支对应的块对象(包括挂起下监听状态变化的副作用函数、执行指令的清理函数和递归触发子块对象的清理操作)constremoveActiveBlock=()=>{if(block){//重新插入锚元素定位插入点parent.insertBefore(anchor,block.el)block.remove()//解引用被销毁的块对象,让GC回收it对应的JavaScript对象和detached元素block=undefined}}//将渲染任务推送到异步任务对立面,在这一轮EventLoop微Queue执行阶段会执行一次ctx.effect(()=>{for(leti=0;i元素,所以我删减了代码所以我把代码删掉了当前块对象和它的子孙一起被移除this.template.parentNode!.removeChild(this.template)this.teardown()}teardown(){//先递归调用子块对象的清理方法this.ctx.blocks.forEach(child=>{child.teardown()})//包含中止副作用函数监控状态变化this.ctx.effects.forEach(stop)//执行指令的清理函数this.ctx.cleanups.forEach(fn=>fn())}}深入了解v-for的工作原理