React diffing算法的简化版实现

这篇博客详细介绍了React的Diffing算法,用于实现高效的组件渲染。文章通过极简版的实现展示了如何在O(n)复杂度下完成节点比对,包括关键的四指针双向匹配策略和key属性的使用。同时,文中提供了测试样例来验证算法的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

diffing算法是React实现增量渲染的关键。当state或props更新时,render()函数被调用来渲染新的组件。React需要一种方法来高效地渲染,即尽可能复用组件,而不是推倒重来。

树上编辑距离算法(太复杂了看不懂)提供了一种在O(n³)复杂度(n是树上元素个数)内得到所需的最少状态转移数的方法。而这一复杂度对于React的实时性要求是不可接受的。

React基于两个简化实现了O(n)的diffing算法:

1.两棵树的根标签不同,则两棵树不同(即使子节点都相同);

2.可以通过手动指定key属性来标识不同的节点。

diffing算法的关键在于使用虚拟节点(Vnode),尽量不创建真实节点;并使用新旧头尾四个指针来进行双向匹配,非常酷

以下是diffing算法的(极简版)实现与测试样例。

  1 export function patch(oldVnode, newVnode) {  2     if (newVnode.text && newVnode.children && newVnode.children.length) {  3         throw new Error('text and children cannot both exist')  4     }  5     if (!oldVnode.sel) { //old node is a DOM,wrap it  6         oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode)  7     }  8     if (isSame(oldVnode, newVnode)) {  9         patchVnode(oldVnode, newVnode) 10     } else { 11         //replace the old node with new one 12         let newDOM = createElement(newVnode); 13         if (oldVnode.elm) { 14             oldVnode.elm.parentNode.insertBefore(newDOM, oldVnode.elm) 15         } 16         oldVnode.elm.parentNode.removeChild(oldVnode.elm) 17     } 18 } 19  20 function patchVnode(oldVnode, newVnode) { 21     if (oldVnode === newVnode) { 22         return 23     } 24     newVnode.elm = oldVnode.elm 25     if (newVnode.text) { 26         if (newVnode.text !== oldVnode.text) { 27             oldVnode.elm.innerText = newVnode.text 28         } 29     } else { 30         if (oldVnode.text) { 31             oldVnode.elm.innerText = null 32             for (let child of newVnode.children) { 33                 oldVnode.elm.appendChild(createElement(child)) 34             } 35         } else { 36             //both old and new nodes has children,do the diffing! 37             diff(oldVnode.elm, oldVnode.children, newVnode.children) 38         } 39     } 40 } 41  42 // the main diffing algorithm 43 function diff(parent, olds, news) { 44     //use four pointers 45     let new1 = 0 46     let old1 = 0 47     let new2 = news.length - 1 48     let old2 = olds.length - 1 49     let old1Vnode = olds[new1] 50     let old2Vnode = olds[old2] 51     let new1Vnode = news[old1] 52     let new2Vnode = news[new2] 53     let oldKeyMap = {} 54     while (new1 <= new2 && old1 <= old2) { 55         //skip all undefined 56         if (!old1Vnode) old1Vnode = olds[++old1] 57         else if (!old2Vnode) old2Vnode = olds[--old2] 58         else if (!new1Vnode) new1Vnode = news[++new1] 59         else if (!new2Vnode) new2Vnode = news[--new2] 60         //four shortcuts 61         else if (isSame(old1Vnode, new1Vnode)) { 62             patchVnode(old1Vnode, new1Vnode) 63             old1Vnode = olds[++old1] 64             new1Vnode = news[++new1] 65         } else if (isSame(old2Vnode, new2Vnode)) { 6 江苏党政培训 www.jxganxun.cn 6             patchVnode(old2Vnode, new2Vnode) 67             old2Vnode = olds[--old2] 68             new2Vnode = news[--new2] 69         } else if (isSame(old1Vnode, new2Vnode)) { 70             patchVnode(old1Vnode, new2Vnode) 71             parent.insertBefore(old1Vnode.elm, old2Vnode.elm.nextSibling) 72             old1Vnode = olds[++old1] 73             new2Vnode = news[--new2] 74         } else if (isSame(old2Vnode, new1Vnode)) { 75             patchVnode(old2Vnode, new1Vnode) 76             parent.insertBefore(old2Vnode.elm, old1Vnode.elm) 77             old2Vnode = olds[--old2] 78             new1Vnode = news[++new1] 79         } else { 80             //use a map to record key to OldIdx 81             //if all shortcuts failed, use the map to locate the next Vnode(new1Vnode) 82             for (let i = old1; i <= old2; i++) { 83                 let k = olds[i]?.key 84                 if (k) { 85                     oldKeyMap[k] = i 86                 } 87             } 88             let idxInOld = oldKeyMap[new1Vnode.key] 89             if (idxInOld) { 90                 //an existing node. move it to the right place 91                 let toRemoved = olds[idxInOld] 92                 if (toRemoved.sel !== new1Vnode.sel) { 93                     parent.insertBefore(createElement(new1Vnode), old1Vnode.elm) 94                 } else { 95                     patchVnode(toRemoved, new1Vnode) 96                     olds[idxInOld] = undefined 97                     parent.insertBefore(toRemoved.elm, old1Vnode.elm) 98                 } 99             } else {100                 //a brand-new node101                 parent.insertBefore(createElement(new1Vnode), old1Vnode.elm)102             }103             new1Vnode = news[++new1]104         }105     }106     if (new1 <= new2) {107         let before = news[new2 + 1] ? news[new2 + 1].elm : null108         for (let i = new1; i <= new2; i++) {109             parent.insertBefore(createElement(news[i]), before)110         }111     }112     if (old1 <= old2) {113         for (let i = old1; i <= old2; i++) {114             if (olds[i]) {115                 parent.removeChild(olds[i].elm)116             }117         }118     }119 }120 121 // convert virtualDOM to DOM recursively122 function createElement(vnode) {123     let dom = document.createElement(vnode.sel);124     //if is textNode125     if (vnode.text && !(vnode.children && vnode.children.length)) {126         dom.innerText = vnode.text127     } else if (Array.isArray(vnode.children) && vnode.children.length) {128         for (let ch of vnode.children) {129             let childDOM = createElement(ch);130             dom.appendChild(childDOM)131         }132     }133     vnode.elm = dom134     return dom135 }136 137 export function h(sel, data, children) {138     if (arguments.length !== 3) {139         throw new Error('#arg error')140     }141     if (isPrimitive(children)) {142         return vnode(sel, data, undefined, children.toString(), undefined)143     } else if (Array.isArray(children)) {144         let c = []145         for (let i = 0; i < children.length; i++) {146             if (isPrimitive(children[i])) {147                 children[i] = vnode(undefined, undefined, undefined, children[i], undefined)148             }149             c.push(children[i])150         }151         return vnode(sel, data, c, undefined, undefined)152     } else if (typeof children === 'object' && children.sel) {153         return vnode(sel, data, [children], undefined, undefined)154     }155     throw new Error('unexpected args')156 }157 158 function isPrimitive(obj) {159     return typeof obj === 'string' ||160         typeof obj === 'number' ||161         obj instanceof String ||162         obj instanceof Number163 }164 165 function vnode(sel, data, children, text, elm) {166     let key = data?.key167     return {sel, data, children, text, elm, key}168 }169 170 function isSame(oldVnode, newVnode) {171     return oldVnode.sel === newVnode.sel && oldVnode.key === newVnode.key172 }

测试(欢迎捉虫):

 1 let container = document.querySelector('#container'); 2 let vnode = h('ul',{},[ 3     h('li',{key:'A'},'A'), 4     h('li',{key:'B'},'B'), 5     h('li',{key:'C'},'C'), 6     h('li',{key:'D'},'D'), 7     h('li',{key:'E'},'E'), 8 ]) 9 10 patch(container,vnode)11 12 let vnode2 = h('ul',{},[13     h('li',{key:'D'},'D'),14     h('li',{key:'E'},'E'),15     h('li',{key:'G'},'G'),16     h('span',{key:'F'},'F'),17     h('li',{key:'A'},'A'),18     h('span',{key:'C'},'C'),19 20 ])21 patch(vnode,vnode2)22 23 let vnode3 = h('ul',{},[24     h('li',{},'啦啦啦'),25     h('li',{},[26         h('div',{},'div11'),27         h('div',{},'div222')28     ])29 ])30 patch(vnode2,vnode3)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值