在《petite-vue源码剖析-v-if和v-for的工作原理》中我们了解了v-for在静态视图中的工作原理,这里我们将详细了解v-for在更新渲染功能时的工作原理.逐行解析//file./src/directives/for.ts/*[\s\S]*表示识别几个空格字符和非空格字符,默认是贪心模式,即`(item,index)invalue`将匹配整个字符串。*改为[\s\S]*?是惰性模式,即`(item,index)invalue`只会匹配`(item,index)`*/constforAliasRE=/([\s\S]*?)\s+(?:in)\s+([\s\S]*?)///用于移除`(item,index)`中的`(`和`)`conststripParentRE=/^\(|\)$/g//匹配`item,index`中的`,index`,然后可以提取value和index独立处理constforIteratorRE=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/typeKeyToIndexMap=Map//为了便于理解,我们假设只接受`v-for="valinvalues"`形式,所有输入参数都是有效的,删除了入参有效性和解构等代码exportconst_for=(el:Element,exp:string,ctx:Context)=>{//通过正则表达式提取`in`两边的子表达式字符串在表达式字符串中constinMatch=exp.match(forAliasRE)//保存下一轮遍历分析的模板节点constnextNode=el.nextSibling//插入锚点,移除element与来自DOM树的`v-for`constparent=el.parentElement!constanchor=newText('')parent.insertBefore(anchor,el)parent.removeChild(el)constsourceExp=inMatch[2].trim()//获取`value`in`(item,index)invalue`letvalueExp=inMatch[1].trim().replace(stripParentRE,'').trim()//Get`(item,index)invalue`in`item,index`letindexExp:字符串|未定义letkeyAttr='key'letkeyExp=el.getAttribute(keyAttr)||el.getAttribute(keyAttr=':key')||el.getAttribute(keyAttr='v-bind:key')if(keyExp){el.removeAttribute(keyExp)//将表达式序列化,比如`value`变成`"value"`,这样就不会参与了随后的表达式计算()//获取`item,index`中的itemindexExp=match[1].trim()//获取`item,index`中的index}letmounted=false//false表示第一次渲染,true表示重新渲染-renderingletblocks:Block[]letchildCtxs:Context[]letkeyToIndexMap:KeyToIndexMap//用来记录key和index的关系,当重新渲染发生时,元素会被重用constcreateChildContexts=(source:unknown):[Context[],KeyToIndexMap]=>{constmap:KeyToIndexMap=newMap()constctxs:Context[]=[]if(isArray(source)){for(leti=0;i{constdata:any={}data[valueExp]=valueindexExp&&(data[indexExp]=index)//为每个子项创建一个单独的项elementScopeconstchildCtx=createScopedContext(ctx,data)//key表达式在对应的子元素范围内运行constkey=keyExp?evaluate(childCtx.scope,keyExp):索引map.set(key,index)childCtx.key=keyreturnchildCtx}//为每个子元素创建一个块对象constmountBlock=(ctx:Conext,ref:Node)=>{constblock=newBlock(el,ctx)block.key=ctx.keyblock.insert(parent,ref)returnblock}ctx.effect(()=>{constsource=evaluate(ctx.scope,sourceExp)//计算`(item,index)initems`中items的真实值constprevKeyToIndexMap=keyToIndexMap//生成一个新的作用域并计算`key`、`:key`或`v-bind:key`;[childCtxs,keyToIndexMap]=createChildContexts(source)if(!mounted){//为每个子元素创建块对象,解析子元素的后代插入DOM树blocks=childCtxs.map(s=>mountBlock(s,anchor))mounted=true}else{//更新渲染逻辑!!//删除根据key更新后不存在的元素for(leti=0;i
`}})//ReactfunctionApp(){constitems=[...]return(items.map(item=>{if(item.type==='span'){return(