您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

Vue3的最低版本实现 {源代码交易}

  • 时间:2022-08-09 00:52 编辑: 来源: 阅读:271
  • 扫一扫,手机访问
摘要:Vue3的最低版本实现 {源代码交易}
导读:我在上一篇文章里讲过Vue3中响应性的原理,对Vue3中响应性的实现原理做了详细介绍。想必大家对Vue3中如何使用Proxy实现数据代理,如何实现数据的响应性都有积极的认识。今天我们又进阶了,来看看它是怎么和视图层连接实现一个低配版的Vue3的。 首先我们要知道Vue和React的整体实现思路都是先把我们编写的模板template或者jsx代码转换成虚拟节点,然后经过一系列的逻辑求解,最后通过render方法挂在指定的节点上,渲染出真正的DOM。因此,我们需要实现的第一步是Render方法,该方法将虚拟DOM节点转换为真实的DOM节点,并将其呈现在页面上。 要实现虚拟节点的渲染方法,首先要有虚拟DOM。这里我们以一个经典计数器为例。 //计数器虚拟节点constvnode = {tag:' div ',Props:{ style:{ textalign:' center ' } },children: [ { tag: 'h1 ',Props: {style: {color:' red'},children:' counter'},{ tag: 'button ',Props:{ onClick:()= & gt;alert('祝贺你!')},小朋友:‘点击我!’}]}这样就可以确定render方法有两个固定的参数,一个是虚拟节点vnode,一个是要渲染的容器container。这里以#app为例。 render方法//render函数导出函数render (vnode,container) {//render解决方案函数patch(null,vnode,container);}render只是接收初始化的参数。参考源代码,我们还构建了一个用于渲染的补丁方法。 Patch方法//渲染函数patch (n1,n2,container){//如果是普通标签If(type of N2 . tag = = ' string '){//挂载元素mountElement(n2,container);} else If(type of n2 . tag = = = ' object '){//如果是组件}}patch方法不仅用于初始渲染,还用于后续的升级操作,因此需要三个参数,即旧节点n1、新节点N2和容器container。 此外,考虑到标签和组件两种形式,我们需要单独进行判断。让我们从简单的标签开始。 MountElment方法mountElment方法是挂载普通元素,其核心是递归。 pass:mount元素中经常使用一些dom操作,因此有必要将这些常用的工具方法放入一个runtime-dom.js文件中。 //mount element function mount element(vnode,container) {const {tag,props,children } = vnode//创建一个元素将虚拟节点映射到真实节点,让El =(vnode . El = nodeops . createelement(tag));//求解属性if(Props){ for(let key in Props){ nodeops . hostpatchprops(El,key,{},Props [key]}}//children是数组if(array . is array(children)){ mount children(children,El)} else {//string nodeops . hostsetelementtext(El,children);}//Insert node nodeOps.insert(el,container)}为了解决多个子节点的问题,我们定义一个mountChildren方法进行递归挂载。 mountChildren方法//递归挂载子节点函数mountchildren (children,container) {for(让I = 0;我& lt儿童.长度;i++){ let child = children[I];//递归挂载节点补丁(null,child,container);}}}到目前为止,经过一系列操作,我们已经能够成功地将我们的vnode渲染到页面上了。用手点一下,功能都Ok ~我们在组件挂载上实现了简单的标签挂载。接下来,让我们看看组件挂载是如何实现的。 如前所述,组件的标签是一个object对象。首先,让我们构建一个自设置组件。 //my component const my component = { setup(){ return()= & gt;{//render function return { tag:' div ',props: {style: {color:' blue'}},children: [ { tag: 'h3 ',props: null,children:'我是一个自设置组件' }]}}需要注意的是,Vue3中的setup方法可以返回一个函数,即渲染函数,来宣告组件。有关详细信息,请参考文档。 Pass:如果我们不返回渲染函数,vue会把模板编译到渲染函数中,然后把结果挂载到setup的返回值中。 我们把MyComponent放在我们的vnode中,它的结构如下:{tag: MyComponent,props: null,//组件children的属性:null // slot }pass:我们这里暂时不考虑props和children,也就是对应组件的props属性和组件的slot slot。 挂载过程mountComponent方法组件大致如下:首先构建一个组件实例作为组件的上下文,调用组件的setup方法返回render函数,调用render获取组件的虚拟节点,最后通过patch方法渲染到页面。 //挂载组件functionmountcomponent (vnode,container){//根据component = {vnode: vnode,//虚拟节点render: null,setup子树的返回值:null,由//render返回的结果}//公告组件const Component = vnode.tag//调用setup返回render instance . render = component . setup(vnode . props,instance);//调用render返回子树instance . subtree = instance . render & amp;instance . render();//渲染组件补丁(null,instance.subtree,container)}最后只要把mountComponent方法放到vnode.tag === "object "分支中,就可以顺利得到结果。 在数据响应上,我们实现了常用标签和组件的渲染操作,事件也是简单的告警。然后我们需要用数据联系它。 先公布一个数据作为页面的数据源,const data = {count: 0}然后对前面vnode的children部分做一个简单的修改:{tag: 'h1 ',Props: {style: {color:' red'}},children:' counter,current value:'+data.count},{tag:' button ',Props:{ onclick:()= >;data.count++ },children: 'Increment!'},{ tag: 'button ',props:{ onClick:()= & gt;data.count - },children: 'Minus!'}渲染结果如下图所示:现在,我们想要实现的需求很简单。当我们单击递增和递减按钮时,当前计数值将增加1或减少1。 然而,事实上,当我们点击时,页面根本没有变化。其实count的价值已经升级了。可以做个断点看看。 造成这个结果的原因是我们还没有把视图和我们的数据联系起来,也就是我们缺少一个桥梁,类似于vue2中的watcher。 这时候就需要用到vue3中的两种反应方法——反应法和效果法,用来收集数据和进行反应。请戳一下Vue3里的反应原理,这里就不赘述了,直接用就好。 首先通过reactive方法,用proxy:const data = reactive({ count:0 })表示数据,然后用effect方法链接:effect(()= >;{ const vnode = { tag: 'div ',props:{ style:{ textAlign:' center ' } },children: [ { tag: 'h1 ',props: { style: { color: 'red' } },Children:' counter,current value:'+data.count},{tag:' button ',props:{ onclick:()= >;data.count++ },children: 'Increment!'},{ tag: 'button ',props:{ onClick:()= & gt;data.count - },children: 'Minus!'},{ tag: MyComponent,props: null,//组件的属性,children:null//slot }]} render(vnode,app)})通过effect进行包装,然后reactive收集依赖关系,可以达到数据和视图链接的效果。 我们来试一下,可以看到以下结果:count的结果是正确的,但是我们发现无论我们是点击increment还是minus,都会重新创建一个新的vnode,并插入到页面中。这是因为我们暂时没有做dom-diff,后面会处理这个问题。 组件的部分升级。我们先来看组件中的一个问题。首先我们给data添加一个新的num属性,然后修改自己的设置组件如下:{tag:' div ',props: {style: {color:' blue'}},Children: [{tag:' H3 ',props: null,Children:'我是一个自我设置组件,num:'+data.num},{ tag : 'button ',props:{ onClick:()= & gt;{ data . num++;} },children:'升级num'} ]}然后我们在页面上点击升级num的按钮,可以看到类似上面提到的升级count的结果,也就是这个问题。我们升级组件的num,原则上与count无关,所以应该只升级与count相关的dom。 因此,我们需要收集每个组件的依赖关系来实现组件的部分刷新。 只是在我们组装补丁的时候加上effect:effect(()= >;{//调用setup返回render instance . render = component . setup(vnode . props,instance);//调用render返回子树instance . subtree = instance . render & amp;instance . render();//渲染组件补丁(null,instance.subtree,container)},从而实现组件的部分升级。 dom-diff导致上面的数据升级,页面一直追加的原因是DOM-DIFF没有做。接下来我们来聊聊,做个简单的dom-diff。 通过:作者能力有限,与众不同;组件中的组件暂时不考虑;以李为例来说明diff: constold vnode = {tag:' ul ',props: null,children: [{tag:' li ',props: { style: { color: 'red' },key: 'A' },children: 'A' },{tag:' li ',props: { style: { color: 'orange' },key: 'B' },children: 'B' },]}render(oldVNode,app)setTimeout(()= & gt;{ const newVNode = { tag: 'ul ',props: null,children: [ { tag: 'li ',props: { style: { color: 'red' },key: 'A' },children: 'A' },{ tag: 'li ',props: { style: { color: 'orange' },key: 'B' },children: 'B' },{ tag: 'li ',props: { style: { color: 'blue' },key: 'C' },children: 'C' },{ tag: 'li ',props: { style: { color: 'green' },key: 'D' },children: 'D' }。 500)以上vnode的意思是先渲染oldVNode得到三个不同的li,A,B,E,1.5s后先修改B的颜色属性,然后删除E,最后增加两个新的li,C,D。 涉及dom复用(A)、属性修改(B)、删除(E)和新增(C、D);PatchProps方法首先看最简单的Props属性的比较运算。它的思路是添加新添加的属性,用新值替换旧属性值,删除到旧的和新的。 函数patchProps (el,oldProps,newProps) { if ( oldProps!== newProps) {/* 1。将新属性设置为*/ for (let key in newProps) {//旧属性值const prev = old props[key];//新属性值const next = new props[key];if ( prev!== next) {//设置新值nodeops.hostpatchprops (El,key,prev,next)}}/* 2。删除旧的,但新的*/ for (let key in oldProps) {if(!new props . hasownproperty(key)){//清空新属性nodeops.hostpathprops (El,key,old props [key],null)}}}这样就完成了属性的比较,接下来是子元素的比较。 patchChildren方法的子元素比较可以分为以下几种情况:新节点的子元素是简单的字符串,直接替换字符串就行了,在对应的节点上设置新的文本;否则,新节点是数组。也有两种情况:第一,如果旧节点是简单的字符串,只需要删除旧节点,挂载新节点即可。 第二,旧节点也是一个数组。在最复杂的情况下,需要将新节点与旧节点逐一进行比较。 //子元素与函数patch子元素(n1,n2,container){ const C1 = n1 . children;const c2 = n2.childrenif(type of C2 = = ' string '){//new的子元素是字符串,文本替换if (c1!= = C2){ nodeops . hostsetelementtext(容器,C2);} } else {//new的子元素是数组if (typeof c1 == "string") {//先删除old的原内容,再插入新内容nodeops . hostsetelementtext(container,' ');//挂在新的子挂载子上(C2,容器);} Else {//新旧的子节点都是数组}}以上方法可以完成简单的文本替换和新节点挂载。对于新旧元素的子元素都是数组的情况,需要用patchKeyChildren方法实现。 PatchKeyChildren方法((暂时不考虑key))这种方法首先根据新节点的key生成一个索引映射表,然后去旧节点查找是否有对应的元素,如果有则重用,然后删除旧节点中多余的部分,在新节点中添加新添加的部分,最后通过确定key和属性值来确定是否可以移动。 官方源代码使用最长递增子序列LIS算法,用于确定不需要移动的元素的索引,提高性能。 函数Patchkeychildren (C1,C2,容器){//1。根据新节点,生成与该关键字对应的索引的映射表E1 = C1 . length-1;//旧的最后一个索引let E2 = C2 . length-1;//new last index//constkeytoneindexmap = new map();for(设I = 0;我& lt= e2i++){ const current le = C2[I];//当前元素keytonewindexmap . set(current le . props . key,i)}//2。找出旧节点是否有对应的键,如果有,重用constnewindextoolindexmap = new array(E2+1);//用于标识哪个元素已经被修补(设I = 0;我& lt= e2i++)newIndexToOldIndexMap[I]=-1;for(设I = 0;我& lt= e1i++){ const old vnode = C1[I];//新索引let new index = keytonewindexmap . get(old vnode . props . key);if(new index = = = undefined){//old Yes,没有nodeops . remove(old vnode . El)///直接删除旧节点} else {//多路复用//比较属性newindextoolindexmap[new index]= I+1;patch(oldVNode,c2[newIndex],容器);} } let sequence = get sequence(newIndexToOldIndexMap);//获取最长的序列号let j = sequence . length-1;//获取最后一个索引。//以上方法只比较和删除无用节点,不移动。//从后向前插入for(设I = E2我& gt= 0;I-){ let current le = C2[I];常量锚=(I+1 & lt;= e2)?c2[i + 1]。El:null;//新节点比旧的if(newindextoolindexmap[I]= =-1){//新元素,需要插入列表补丁(null,currentele,container,anchor);//在anchor前插入} else {//获取最长的递增子序列,确定不需要移动的元素。直接跳过if(I = = = sequence[j]){ j-;} else {//插入元素nodeops . Insert(current le . El,container,anchor);}} }}getSequence算法源代码,请戳。 我们先在原vnode的children子元素的props中添加相应的key元素,然后再尝试一次,就ok了~综上所述,我们实现了一个非常简单的vue3版本,实现了基本的vnode渲染和简单的dom-diff操作,让我们对vue3的内部实现有了一个积极的认识。 vue3真正的内部实现远比这个复杂,有很多代码实现的思路和方法个人很难理解,都值得借鉴。 这篇文章也是我在学习vue3过程中的知识积累和个人记录。希望可以作为大家有价值的参考。加油~最后附上github的地址,希望大家批评。


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)
【技术支持|常见问题】别告诉我你没看过邰方这两则有思想的创意广告! (2022-11-04 10:37)
【技术支持|常见问题】你正确使用https了吗? [php源码](2022-11-04 10:37)
【技术支持|常见问题】安全超文本传输协议 {源码分享}(2022-11-04 10:37)
【技术支持|常见问题】借助图文解锁HTTPS原理,10分钟还原HTTPS。真的很像!建筑师必读 {源码分享}(2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部