您好!欢迎来到爱源码

爱源码

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

ElTree组件的内部原理 《免费源码》

  • 时间:2022-07-09 00:15 编辑: 来源: 阅读:311
  • 扫一扫,手机访问
摘要:ElTree组件的内部原理 《免费源码》
ElTree组件的内部原理最近一直在开发Element3的树组件。这里记录一下Tree component的思路和内部实现原理,对想学习源代码的童鞋也是一个帮助。 设计思想在设计树组件时,两棵树被用来相互映射。一棵树是由客户自己设置的节点组成的tree RawNode,另一棵树是内部渲染的tree TreeNode。 当RawNode中某个节点的值发生变化时,Mapper会得到通知,然后TreeNode会根据通知的内容发生变化。 整个核心组件的难点在于Mapper,它需要完成:节点转换和映射变化来监控和响应变化。这里需要注意两点:Mapper在RawNode发生变化后需要修改TreeNode,但修改TreeNode后,无法通知变化来修改RawNode。在监视节点时,它需要监视存储子节点阵列。这就引出了一个重要的问题:如何监控一个节点的变化?这里我们就来说说ES6的一个新的类代理。顾名思义,就是代理。它可以阻塞一个对象,比如:读取属性,阻塞get,修改属性,阻塞set,删除属性,阻塞deleteProperty等。在https://es6.ruanyifeng.com/#docs/Proxy,这里代理的另一个特点是,当修改原始对象时,它不会触发阻塞,这是一个处理它的好方法。Note 1Proxy还有一个特点就是每次修改前一定会触发get操作。因为是先获得的,所以在修改测试之前会先进行TDD测试。我们先想想需要什么方法,然后假设在测试中用到,再写这个方法。其实很多时候,我们的内心都是这样的。每次开发一个类或函数,都要先考虑接口,然后再实现。TDD只是把你内心的活动带到现实中来,这有几个好处。1.您可以稍后自动测试它。2.可以整理一下界面。3.当你在代码上有大动作的时候,他能起到积极的引导作用。这一点在树的发展过程中深有感触。首先,我们需要一个TreeNode类作为树的节点,一个可以监听对象的工具类观察器,一个可以通知事件的工具类事件,以及一个对象映射映射器类TreeNode规范。我们需要传入id、label、children来创建节点,也是描述(' treenode.js ',()= >;{ it('初始化一个节点',()= & gt{//初始化一个节点construot = newtreenode(1,' node1 ',[newtreenode (2,' node2 ',[]) ]) expect(root.id)。toBe(1) expect(root.label)。toBe(' node 1 ')expect(root . children[0])。id)。toBe(2) Expect(根。儿童[0]。标签)。tobe ('node')}) WatcherSpec在这里,我们要阻塞对象的操作,这样才能知道这个对象中的哪个属性发生了改变,改变了什么值。监听存储子节点的数组节点的添加节点的修改(这里需要注意的是修改是指针变化)。删除节点的描述(' watcher . js){ it(' listern a node prop change ',()= & gt{ const root = { label: 'Node1 ',} const watcher = new watcher(root)const _ root = watcher.proxy//The对象获取代理后const change handler = jest . fn()watcher . bind handler(' change ',change handler)const addHandler = jest . fn()watcher . bind handler(' add ',addHandler)_ root . label = " Test " expect(change handler)。toHaveBeenCalledTimes(1)_ root . disabled = true expect(addHandler)。toHaveBeenCalledTimes(1) })它('监听一个节点子节点指针和长度的变化',()= & gt{ const root = { label: 'Node1 ',children:[{ label:' node 2 ' }]} const watcher = new watcher(root)const _ root = watcher.proxy//The对象获取代理const childrenChangeHandler = jest . fn()watcher . bind handler(' array/change ',childrenChangeHandler)const addHandler = jest . fn()watcher . bind handler(' array/',addHandler)_ root . label = ' Test ' expect(childrenChangeHandler)。toHaveBeenCalledTimes(1)expect(childrenChangeHandler)。tohavebeenntcalledwith(1,{ target: root,key: 'label ',value: 'Test ',current node:root })_ root . disabled = true expect(addHandler)。toHaveBeenCalledTimes(1)expect(childrenChangeHandler)。tohavebeenntcalledwith(1,{ target: root,key: 'disabled ',value: true,Current Node: root}})事件规范在这里,实现简单的事件监控和发送,还可以了解订阅和推送describe ('event.js ',()= >;{ it('侦听事件',()= & gt{ const Event = new Event()const CB = jest . fn()Event . on(' ev1 ',cb) event.emit('ev1 ',1,2,3) Expect (CB)。tohavebeencalledtimes(1)Expect(CB)。这里的Tohavebeencalledwith (1,2,3)}) Mapper规范是上述所有函数的最终集成。节点转换和映射更改监视器响应更改描述(' mapper.js ',()= >;{ it('映射树',()= & gt{ const rawNode = { text: 'Node1 ',childs: [ { text: 'Node11 ',childs: [ { text: 'Node111 ',childs:[]}]}]} const mapper = new tree mapper(rawNode,{ label: 'text ',childs:' childs ' })const rawNode proxy = mapper . rawNode proxy const treenode proxy = mapper . treenode proxy rawNode proxy . text = " Test " expect(rawNode proxy . tex t)。to equal(treenode proxy . label)expect(rawnode proxy . childs[0]。正文)。to equal(treenode proxy . children[0])。label)expect(rawnodeproxy . childs[0])。查尔兹[0]。正文)。to equal(treenode proxy . children[0])。儿童[0]。标签)})...})TreeNode的实现原理ES6的这个类只是实现了TreeNode类treenode {constructor (ID,label,children){ This . ID = ID This . label = label;this.children =孩子??[];}}Watcher这里代理主要是被Proxy阻塞,然后被Event推出,class Watcher { constructor(target){ this . Event = new Event();//用于变更通知this . to proxy = new weak map();// WeakMap有一个特别好的特性,可以自动移除未引用的对象this . toraw = new weak map();this . proxy = this . reactive(target,target);} reactive (target,last target){//嵌套responsive if(!is(target)| | | this . toraw . has(target)){//如果当前是代理,则返回目标,如果不是对象,则返回目标;} If(this . to proxy . has(target)){//如果当前对象和代理返回this . to proxy . get(target);} const current node = is array(last target)?target:last target;//获取当前节点const handler = { Get:this . create getter(),set:this . create setter(current node),delete property:this . createdeleteproperty(current node)};const observer =新代理(目标,处理程序);this.toProxy.set(目标,观察者);//建立原始对象和代理this.toRaw.set(observer,target);//对象返回观察者的映射关系;} bindHandler(type,callback) {//绑定一个通知this.event.on(type,callback);} createGetter() { return (target,key)= & gt;{ const res = Reflect.get(target,key);返回isObject(res)?this.reactive(res,target):RES;//如果是对象,继续嵌套代理,如果不是对象,返回此值};} create setter(current node){ return(target,key,value)= & gt;{if (this.toRaw.has(value)) {//如果被写对象已经被代理表示,先转换成普通对象value = this . toraw . get(value);} if(isArray(target)){ if(key = = = " length "){ this . event . emit(" array/changelongth ",{ target,key,value,currentNode,});} else {if (reflect.has (target,key)){//Modify this . event . emit(" array/change ",{target,key,value,currentNode,});} else {//新增this.event.emit ("array/append ",{target,key,value,currentnode,});}}} else {if (reflect.has (target,key)){//modify this . event . emit(" change ",{target,key,value,current node });} else {//Add this . event . emit(" Add ",{target,key,value,current node });} }返回Reflect.set(target,key,value);};} createDeleteProperty(current node){ return(target,key)= & gt;{ if(is array(target)){ this . event . emit(" array/delete ",{ target,key,current node });} else { this.event.emit("delete ",{ target,key,current node });} return reflect . delete property(target,key);};}}}如果你看过Vue3的反应性库,你会发现我这里的守望者有点类似于反应性。其实守望者内部只是借用了Vue3Reactive的几个实现。 Event只是实现一个事件通知类event { constructor(){ this . events = new map();} on(name,callback) { if(!this . events . has(name)){ this . events . Set(name,new Set([回调]);返回;} this.events.get(name)。添加(回调);} emit(名称,...args){ if(this . events . has(name)){ this . events . get(name)。forEach((CB)= & gt;cb(...args));} }}Mapper,这个组件的实现,大概是以下几个步骤:1。Convert raw node->: TreeNode2,Watcher监控RawNode 3,Watcher监控TreeNode 4,更改通知后,修改原数据class mapper { constructor(raw node,keymap) {this。to treenode = newweakmap();this . torawnode = new weak map();this.toRawNodeKey = keyMapthis . totreenodekey = reversalNodeKeyMap(key map);//reverse NodeKey //初始化this . raw node = raw node;this . treenode = this . converttotreenode(rawNode);//生成treenode this . rawnode watcher = new watcher(this . rawnode);this.treeNodeWatcher =新观察器(this . treenode);this . with rawnodehandler();this . with treenode handler();//分别响应解析rawNode和treeNode } Converttotreenode(rawnnode){ consttreenode = newtreenode(rawnnode[this . torawnodekey . id],rawNode[this . torawnodekey . label],this . converttotreenodes(rawNode[this . torawnodekey . children]),{ is checked:rawNode[this . torawnodekey . is checked]});this.toTreeNode.set(rawNode,treeNode);this.toRawNode.set(treeNode,rawNode);返回treeNode} convertToRawNode(treeNode){ const rawNode = {[this . torawnodekey . id]:treeNode . id,[this . torawnodekey . label]:treeNode . label,[this . torawnodekey . children]:this . converttorawnodes(treeNode . children),};this.toTreeNode.set(rawNode,treeNode);this.toRawNode.set(treeNode,rawNode);返回rawNode} convertToTreeNodes(raw nodes){ return raw nodes?。map((node)= & gt;this . converttotreenode(node));} convertToRawNodes(treeNodes){ return treeNodes?。map((node)= & gt;this . converttorawnode(node));} with rawnodehandler(){ this . rawnodewatcher . bind handler(" array/append ",({ currentNode,value })= & gt;{ const current treenode = this . to treenode . get(current node);this . fortreenodappendchild(current treenode,this . converttotreenode(value));} );this . rawnodewatcher . bind handler(" array/delete ",({ currentNode,key })= & gt;{ const current treenode = this . to treenode . get(current node);this . fortreenoderemovechild(current treenode,key);});this . rawnodewatcher . bind handler(" array/change ",({ currentNode,key,value })= & gt;{ const current treenode = this . to treenode . get(current node);this . fortreenodeupdatechild(current treenode,key,this.toTreeNode.get(value)??this . converttotreenode(value));} );this . rawnodewatcher . bind handler(" change ",({ currentNode,key,value })= & gt;{ const current treenode = this . to treenode . get(current node);this . fortreenodeupdatevalue(current treenode,this.toTreeNodeKey[key],value);});this . rawnodewatcher . bind handler(" add ",({ currentNode,key,value })= & gt;{ const current treenode = this . to treenode . get(current node);this . fortreenodeupdatevalue(current treenode,this.toTreeNodeKey[key],value);});} with treenode handler(){ this . treenode watcher . bind handler(" array/append ",({ currentNode,value })= & gt;{ const current awnode = this . torawnode . get(current node);this . forrawnode appendchild(current awnode,this . converttorawnode(value));} );this . treenode watcher . bind handler(" array/delete ",({ currentNode,key })= & gt;{ const current awnode = this . torawnode . get(current node);this . forrawnoderemovechild(current awnode,key);});this . treenode watcher . bind handler(" array/change ",({ currentNode,key,value })= & gt;{ const current awnode = this . torawnode . get(current node);this . forrawnodeupdatechild(current awnode,key,value);} );this . treenode watcher . bind handler(" change ",({ currentNode,key,value })= & gt;{ const current awnode = this . torawnode . get(current node);this . forrawnodeupdatevalue(currentRawNode,this.toRawNodeKey[key],value);} );this . treenode watcher . bind handler(" add ",({ currentNode,key,value })= & gt;{ const current awnode = this . torawnode . get(current node);this . forrawnodeupdatevalue(currentRawNode,this.toRawNodeKey[key],value);});} for treenode appendchild(current treenode,new treenode){ current treenode . children . push(new treenode);} for treenode updatevalue(current treenode,key,value){ if(key = = = " children "){ current treenode[key]= this . converttotereenodes(value);} else { current treenode[key]= value;} } FortreeNodeMoveChild(current treenode,index) {//todo: toRawNode和toTreeNode必须在这里求解,否则内存会泄漏current treenode . children . splice(index,1);} for treenode updatechild(current treenode,index,child node){ current treenode . children[index]= child node;} forrawnode appendchild(currentRawNode,newRawNode){ currentRawNode[this . torawnodekey . children]。push(newRawNode);} forRawNodeUpdateValue(currentRawNode,key,value){ if(key = = = this . torawnodekey[" children "]){ currentRawNode[key]= this . converttorawnodes(value);} else if(reflect . has(current awnode,key)){ current awnode[key]= value;} } forRawNodeRemoveChild(current awnode,index){ current awnode[this . torawnodekey . children]。拼接(索引,1);} forRawNodeUpdateChild(current awnode,index,child node){ current awnode[this . torawnodekey . children][index]= child node;}}}其实开发Tree的时候,比上面的要复杂。这里简化了少量操作。事实上,在真实的树中,TreeNode需要对Vue做出响应。但是在开发过程中,Vue的响应性和Watcher的响应性有少量的冲突,所以会发生少量的坑人的事情。但是,最后还是用了少量的方法来处理。在这里,你可以去学习树的源代码。 这里说了这么多,但我想说的是,当你遇到一个大坑的时候,你可以摆脱它,可以加深你对这个坑所对应的问题的理解。就像摸着坑过河。 最后,真的希望能有更多的人加入我们花果山的团队,和我们一起做开源。拥抱太阳/元素3喜欢,评论和收集image-20210205193035624.png。


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(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)

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