1.【Vue原理】Slot - 源码版之普通插槽
2.vueslot是什么意思?
3.vue/compiler-dom源码分析学习--day4: 字符串化hoist节点
4.Vue原理VNode - 源码版
5.vue3源码分析——实现slots
6.keep-alive的vue2和vue3的源码以及LRU算法
【Vue原理】Slot - 源码版之普通插槽
Vue源码中的Slot机制有助于理解组件间的内容传递,今天我们将深入剖析普通插槽的原理。首先,普通插槽包括默认Slot和具名Slot,它们的主要区别在于是否具有自定义名称,但处理方式相似。缩量公式源码
以一个简单示例开始,我们创建一个父组件,其中包含名为'test'的子组件,它有一个slot区域。插槽内容解析的关键在于,其作用域在父实例上,这意味着slot内的变量会直接引用父实例的属性,如上面例子中的name。
当父组件渲染时,会绑定父实例为执行上下文,test组件内的slot内容会根据with语句访问父实例的变量。解析插槽内容的过程与普通模板节点的解析流程相同,只是访问的是父实例的属性。
接下来,父组件生成的VNode会包含子组件test及其slot。尽管HTML中不会直接出现'test'标签,但Vue会将其视为一个组件。在patch阶段,Vue会根据VNode创建DOM并插入页面。当遇到test组件时,会解析其组件模板,将slot内容存储在组件实例的补码 源码溢出$slot属性中。
最后,test组件的渲染函数中会调用_renderChildren中的slot信息,替换slot占位符,形成最终的渲染逻辑。整个过程可以总结为:插槽内容解析、父组件VNode处理、slot转存至子组件实例以及渲染函数的替换。
以上就是普通插槽在Vue源码中的工作流程,接下来的文章会继续深入讲解其他类型的slot和相关原理。如果你对Vue源码感兴趣,可以查看我们的系列分享:Vue原理Vue源码阅读总结大会 - 序,以及之前关于响应式原理、Props等的文章。
vueslot是什么意思?
(1)v-slot是什么意思?
v-slot是Vue.js框架中用于命名插槽的新语法,它是Vue.js 6版本中的新增特性。原本的插槽语法只能用在无名插槽上,为了使命名插槽的使用更加简洁、直观,Vue.js引入了v-slot。使用v-slot可以快速创建有名插槽,并且可以在子组件中具名使用。
(2)v-slot如何使用?
使用v-slot语法可以在组件中将插槽具名,具名插槽可以在父组件中传递不同的内容,在子组件中使用相同的插槽名即可自动匹配。在父组件中,可以使用v-slot指令加上具名插槽名,来绑定不同的溯源码可靠内容。如果需要默认内容,可以在具名插槽下添加slot子元素。v-slot配合作用域插槽可以更好地传递数据。
(3)v-slot的优势是什么?
v-slot语法使在Vue.js框架中开发有名插槽变得简单清晰,避免了无名插槽的一些不便之处。有名插槽不但可以传递静态内容,还可以通过作用域插槽提供更灵活的数据传递功能。使用v-slot可以提高代码质量以及可读性,使得开发更加方便快捷。
vue/compiler-dom源码分析学习--day4: 字符串化hoist节点
vue/compiler-dom源码解析继续:深入理解字符串化hoist节点
前言:在处理内置指令后,我们今日关注的是@vue/compiler-dom包中的字符串化hoist节点操作。这部分代码在baseCompile方法中找到调用入口,且hoistStatic选项默认为true,尽管没有直接传入参数。
在vue/compiler-sfc/__tests__/compileTemplate.spec.ts的测试用例中,我们发现参数来源。接着,我们追踪到hoistStatic.ts和`walk`函数,这是实现静态提升(static hoisting)的关键,用于优化性能,避免在render function中重复生成和比较不会变化的静态节点。
静态提升允许将不变的元素和文本节点抽离到render函数外,提高渲染效率。例如,一个只包含动态部分的,其静态部分会被提升,渲染时会直接使用字符串拼接,pcm输出 源码而不是每次都重新创建。
现在,我们来看下stringifyStatic方法。该方法在确定节点会被提升到哪个阶段后执行,确保只处理适合的普通元素和文本节点。在transforms/stringifyStatic.ts中,代码负责识别可stringify的子节点,比如v-slot组件是不支持的,但可以hoist。
在`analyzeNode`方法中,逐层递归检查节点,确保所有子节点满足stringify条件。文本节点则有特殊的处理方式,其他情况下,如遇到table元素,可能存在浏览器兼容性问题,导致不能使用innerHTML。
总结`stringifyCurrentChunk`方法,它将识别到的静态块转换为字符串调用节点,替换原始hoist元素。整个过程旨在优化性能,通过字符串化hoist节点,减少不必要的DOM创建和比较。
尽管代码逻辑相对直观,但众多小方法间的跳转可能影响阅读。核心是找到可stringify的最大静态块,并进行替换。防洪网源码关于内置指令和style的处理,也有相应的优化策略,如transformStyle处理静态style为bind类型。
Vue原理VNode - 源码版
深入理解 Vue 源码,VNode 是关键组件。它在 Vue2 的渲染机制中扮演着核心角色,本文将带你探索2.5.版本的 VNode 实际操作。以下是核心内容概要:
首先,VNode 是虚拟DOM,用 JavaScript对象的形式描述真实DOM,以便在不同环境(如浏览器、Node)下保持兼容性,支持服务端渲染等。它通过减少对DOM的直接操作,提高页面性能。
生成 VNode 的过程涉及 Vue 源码的构造函数,看似简单但内容丰富,需要逐步理解。我们通过实例来构建 VNode,它包含了模板的全部信息,包括节点属性、绑定事件、上下文对象等。
VNode 内部存储的信息非常详尽,如普通属性(如data、elm、context和isStatic),以及组件相关的parent、componentInstance和componentOptions。parent用于保存父子组件间的交互数据,componentOptions记录组件选项,如props、事件和slot。
在组件实例中,VNode 存储在_vnode和_$vnode属性中。_vnode用于实时比对更新,而_$vnode则专属于组件实例,存储外壳节点信息。
理解 VNode 的工作原理对于深入学习 Vue 不可或缺,尽管本文可能未能覆盖所有细节,但希望对你理解 Vue 源码有所帮助。如有遗漏或疑问,欢迎交流指正。
vue3源码分析——实现slots
Vue3源码深入解析:揭秘插槽实现机制
插槽在Vue3中扮演着关键角色,它们是组件化开发中的重要特性。让我们通过源码探究,如何在模板中运用和实现各种类型的插槽:普通插槽、具名插槽以及作用域插槽。首先,理解模板中的插槽调用方式是关键,它会转化为render函数中的h函数,生成vnode对象,再通过特定属性(如default)访问。
为了深入理解,让我们从基础用法开始。在组件实例中, slots的default属性就像一个容器,存储用户未传递的插槽内容。为了测试,先准备DOM环境,然后进行实际操作。
通过测试用例,我们可以发现问题并进行编码解决。具名插槽的特性在于支持多个插槽,并且可以为每个插槽指定特定的名字。实现时,只需在renderSlot方法中传入相应名称即可。
作用域插槽则更为灵活,它允许在slot内部传递数据,且数据仅限于该slot范围内。通过测试用例,我们发现如何在代码层面处理数据共享问题,以确保插槽的局部性。
至此,通过一步步的编码实现和测试用例分析,我们已经掌握了插槽的完整工作原理。无论是普通插槽的简单调用,还是具名插槽的命名处理,以及作用域插槽的数据传递,都得到了全面的掌握。整个开发流程顺畅,测试用例也完美通过。
keep-alive的vue2和vue3的源码以及LRU算法
0.LRU算法
LRU(leastrecentlyused)根据数据的历史记录来淘汰数据,重点在于保护最近被访问/使用过的数据,淘汰现阶段最久未被访问的数据
LRU的主体思想在于:如果数据最近被访问过,那么将来被访问的几率也更高
经典的LRU实现一般采用双向链表+Hash表。借助Hash表来通过key快速映射到对应的链表节点,然后进行插入和删除操作。这样既解决了hash表无固定顺序的缺点,又解决了链表查找慢的缺点。
但实际上在js中无需这样实现,可以参考文章第三部分。先看vue的keep-alive实现。
1.keep-alivekeep-alive是vue中的内置组件,使用KeepAlive后,被包裹的组件在经过第一次渲染后的vnode会被缓存起来,然后再下一次再次渲染该组件的时候,直接从缓存中拿到对应的vnode进行渲染,并不需要再走一次组件初始化,render和patch等一系列流程,减少了script的执行时间,性能更好。
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive
从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive属性判断是否需要缓存。
2.vue2的实现实现原理:通过keep-alive组件插槽,获取第一个子节点。根据include、exclude判断是否需要缓存,通过组件的key,判断是否命中缓存。利用LRU算法,更新缓存以及对应的keys数组。根据max控制缓存的最大组件数量。
先看vue2的实现:
exportdefault{ name:'keep-alive',abstract:true,props:{ include:patternTypes,exclude:patternTypes,max:[String,Number]},created(){ this.cache=Object.create(null)this.keys=[]},destroyed(){ for(constkeyinthis.cache){ pruneCacheEntry(this.cache,key,this.keys)}},mounted(){ this.$watch('include',val=>{ pruneCache(this,name=>matches(val,name))})this.$watch('exclude',val=>{ pruneCache(this,name=>!matches(val,name))})},render(){ constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)constcomponentOptions:?VNodeComponentOptions=vnode&&vnode.componentOptionsif(componentOptions){ //checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}vnode.data.keepAlive=true}returnvnode||(slot&&slot[0])}}可以看到<keep-alive>组件的实现也是一个对象,注意它有一个属性abstract为true,是一个抽象组件,它在组件实例建立父子关系的时候会被忽略,发生在initLifecycle的过程中:
//忽略抽象组件letparent=options.parentif(parent&&!options.abstract){ while(parent.$options.abstract&&parent.$parent){ parent=parent.$parent}parent.$children.push(vm)}vm.$parent=parent然后在?created?钩子里定义了?this.cache?和?this.keys,用来缓存已经创建过的?vnode。
<keep-alive>直接实现了render函数,执行<keep-alive>组件渲染的时候,就会执行到这个render函数,接下来我们分析一下它的实现。
首先通过插槽获取第一个子元素的vnode:
constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)<keep-alive>只处理第一个子元素,所以一般和它搭配使用的有component动态组件或者是router-view。
然后又判断了当前组件的名称和include、exclude(白名单、黑名单)的关系:
//checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}functionmatches(pattern:string|RegExp|Array<string>,name:string):boolean{ if(Array.isArray(pattern)){ returnpattern.indexOf(name)>-1}elseif(typeofpattern==='string'){ returnpattern.split(',').indexOf(name)>-1}elseif(isRegExp(pattern)){ returnpattern.test(name)}returnfalse}组件名如果不满足条件,那么就直接返回这个组件的vnode,否则的话走下一步缓存:
const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}如果命中缓存,则直接从缓存中拿vnode的组件实例,并且重新调整了key的顺序放在了最后一个;否则把vnode设置进缓存,如果配置了max并且缓存的长度超过了this.max,还要从缓存中删除第一个。
这里的实现有一个问题:判断是否超过最大容量应该放在put操作前。为什么呢?我们设置一个缓存队列,都已经满了你还塞进来?最好先删一个才能塞进来新的。
继续看删除缓存的实现:
functionpruneCacheEntry(cache:VNodeCache,key:string,keys:Array<string>,current?:VNode){ constcached=cache[key]if(cached&&(!current||cached.tag!==current.tag)){ cached.componentInstance.$destroy()}cache[key]=nullremove(keys,key)}除了从缓存中删除外,还要判断如果要删除的缓存的组件tag不是当前渲染组件tag,则执行删除缓存的组件实例的$destroy方法。
————————————
可以发现,vue实现LRU算法是通过Array+Object,数组用来记录缓存顺序,Object用来模仿Map的功能进行vnode的缓存(created钩子里定义的this.cache和this.keys)
2.vue3的实现vue3实现思路基本和vue2类似,这里不再赘述。主要看LRU算法的实现。
vue3通过set+map实现LRU算法:
constcache:Cache=newMap()constkeys:Keys=newSet()并且在判断是否超过缓存容量时的实现比较巧妙:
if(max&&keys.size>parseInt(maxasstring,)){ pruneCacheEntry(keys.values().next().value)}这里巧妙的利用Set是可迭代对象的特点,通过keys.value()获得包含keys中所有key的可迭代对象,并通过next().value获得第一个元素,然后进行删除。
3.借助vue3的思路实现LRU算法Leetcode题目——LRU缓存
varLRUCache=function(capacity){ this.map=newMap();this.capacity=capacity;};LRUCache.prototype.get=function(key){ if(this.map.has(key)){ letvalue=this.map.get(key);//删除后,再set,相当于更新到map最后一位this.map.delete(key);this.map.set(key,value);returnvalue;}return-1;};LRUCache.prototype.put=function(key,value){ //如果已经存在,那就要更新,即先删了再进行后面的setif(this.map.has(key)){ this.map.delete(key);}else{ //如果map中不存在,要先判断是否超过最大容量if(this.map.size===this.capacity){ this.map.delete(this.map.keys().next().value);}}this.map.set(key,value);};这里我们直接通过Map来就可以直接实现了。
而keep-alive的实现因为缓存的内容是vnode,直接操作Map中缓存的位置代价较大,而采用Set/Array来记录缓存的key来模拟缓存顺序。
参考:
LRU缓存-keep-alive实现原理
带你手撸LRU算法
Vue.js技术揭秘
原文;/post/