资讯专栏INFORMATION COLUMN

React源码分析与实现(三):实操DOM Diff

Drummor / 1943人阅读

摘要:速度略有损失,但可读性大大提高。与传统对比传统的算法通过循环递归每一个节点,进行对比,这样的操作效率非常的低,复杂程度其中标识树的节点总数。

原文链接:Nealyang PersonalBlog

</>复制代码

  1. 由于源码中diff算法掺杂了太多别的功能模块,并且dom diff相对于之前的代码实现来说还是有些麻烦的,尤其是列表对比的算法,所以这里我们多带带拿出来说他实现
前言

众所周知,React中最为人称赞的就是Virtual DOM和 diff 算法的完美结合,让我们可以不顾性能的“任性”更新界面,前面文章中我们有介绍道Virtual DOM,其实就是通过js来模拟dom的实现,然后通过对js obj的操作,最后渲染到页面中,但是,如果当我们修改了一丢丢东西,就要渲染整个页面的话,性能消耗还是非常大的,如何才能准确的修改该修改的地方就是我们diff算法的功能了。

其实所谓的diff算法大概就是当状态发生改变的时候,重新构造一个新的Virtual DOM,然后根据与老的Virtual DOM对比,生成patches补丁,打到对应的需要修改的地方。

这里引用司徒正美的介绍

</>复制代码

  1. 最开始经典的深度优先遍历DFS算法,其复杂度为O(n^3),存在高昂的diff成本,然后是cito.js的横空出世,它对今后所有虚拟DOM的算法都有重大影响。它采用两端同时进行比较的算法,将diff速度拉高到几个层次。紧随其后的是kivi.js,在cito.js的基出提出两项优化方案,使用key实现移动追踪及基于key的编辑长度距离算法应用(算法复杂度 为O(n^2))。但这样的diff算法太过复杂了,于是后来者snabbdomkivi.js进行简化,去掉编辑长度距离算法,调整两端比较算法。速度略有损失,但可读性大大提高。再之后,就是著名的vue2.0snabbdom整个库整合掉了。
与传统diff对比

传统的diff算法通过循环递归每一个节点,进行对比,这样的操作效率非常的低,复杂程度O(n^3),其中n标识树的节点总数。如果React仅仅是引入传统的diff算法的话,其实性能也是非常差的。然而FB通过大胆的策略,满足了大多数的性能最大化,将O(n^3)复杂度的问题成功的转换成了O(n),并且后面对于同级节点移动,牺牲一定的DOM操作,算法的复杂度也才打到O(max(M,N))。

实现思路

这里借用下网上的一张图,感觉画的非常赞~

大概解释下:

额。。。其实上面也已近解释了,当Virtual DOM发生变化的时,如上图的第二个和第三个 p 的sonx被删除了,这时候,我们就通过diff算法,计算出前后Virtual DOM的差异->补丁对象patches,然后根据这个patches对象中的信息来遍历之前的老Virtual DOM树,对其需要更新的地方进行更新,使其变成新VIrtual DOM。

diff 策略

Web UI中节点跨级操作特别少,可以忽略不计

拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。(哪怕一样的而我也认为不一样 -> 大概率优化)

对于同一层级的一组子节点,他们可以通过唯一的key来区分,以方便后续的列表对比算法

基于如上,React分别对tree diff、Component diff 、element diff 进行了算法优化。

tree diff

基于策略一,React的diff非常简单明了:只会对同一层次的节点进行比较。这种非传统的按深度遍历搜索,这种通过大胆假设得到的改进方案,不仅符合实际场景的需要,而且大幅降低了算法实现复杂度,从O(n^3)提升至O(n)。

基于此,React官方并不推荐进行DOM节点的跨层级操作 ,倘若真的出现了,那就是非常消耗性能的remove和create的操作了。

</>复制代码

  1. 我是真的不会画图

Component diff

由于React是基于组件开发的,所以组件的dom diff其实也非常简单,如果组件是同一类型,则进行tree diff比较。如果不是,则直接放入到patches中。即使是子组件结构类型都相同,只要父组件类型不同,都会被重新渲染。这也说明了为什么我们推荐使用shouldComponentUpdate来提高React性能。

大概的感觉是酱紫的

list diff

对于节点的比较,其实只有三种操作,插入、移动和删除。(这里最麻烦的是移动,后面会介绍实现)。当被diff节点处于同一层级时,通过三种节点操作新旧节点进行更新:插入,移动和删除,同时提供给用户设置key属性的方式调整diff更新中默认的排序方式,在没有key值的列表diff中,只能通过按顺序进行每个元素的对比,更新,插入与删除,在数据量较大的情况下,diff效率低下,如果能够基于设置key标识尽心diff,就能够快速识别新旧列表之间的变化内容,提升diff效率。

对于这三种理论知识可以参照知乎上不可思议的 react diff的介绍。

算法实现

前方高清多码预警

diff

这里引入代码处理我们先撇开list diff中的移动操作,先一步一步去实现

根据节点变更类型,我们定义如下几种变化

</>复制代码

  1. const ATTRS = "ATTRS";//属性改变
  2. const TEXT = "TEXT";//文本改变
  3. const REMOVE = "REMOVE";//移除操作
  4. const REPLACE = "REPLACE";//替换操作
  5. let Index = 0;

解释下index,为了方便演示diff,我们暂时没有想react源码中给每一个Element添加唯一标识

</>复制代码

  1. var ReactElement = function(type, key, ref, self, source, owner, props) {
  2. var element = {
  3. // This tag allow us to uniquely identify this as a React Element
  4. $$typeof: REACT_ELEMENT_TYPE,//重点在这里
  5. // Built-in properties that belong on the element
  6. type: type,
  7. key: key,
  8. ref: ref,
  9. props: props,
  10. // Record the component responsible for creating this element.
  11. _owner: owner,
  12. };
  13. return element;
  14. };
  15. ...
  16. "use strict";
  17. // The Symbol used to tag the ReactElement type. If there is no native Symbol
  18. // nor polyfill, then a plain number is used for performance.
  19. var REACT_ELEMENT_TYPE =
  20. (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) ||
  21. 0xeac7;
  22. module.exports = REACT_ELEMENT_TYPE;

我们遍历每一个VDom,以index为索引。注意这里我们使用全局变量index,因为遍历整个VDom,以index作为区分,所以必须用全局变量,当然,GitHub上有大神的实现方式为{index:0},哈~引用类型传递,换汤不换药~

开始遍历

</>复制代码

  1. export default function diff(oldTree, newTree) {
  2. let patches = {};
  3. // 递归树, 比较后的结果放到补丁包中
  4. walk(oldTree, newTree, Index, patches)
  5. return patches;
  6. }

</>复制代码

  1. function walk(oldNode, newNode, index, patches) {
  2. let currentPatch = [];
  3. if(!newNode){
  4. currentPatch.push({
  5. type:REMOVE,
  6. index
  7. });
  8. }else if(isString(oldNode) && isString(newNode)){
  9. if(oldNode !== newNode){// 判断是否为文本
  10. currentPatch.push({
  11. type:TEXT,
  12. text:newNode
  13. });
  14. }
  15. }else if (oldNode.type === newNOde.type) {
  16. // 比较属性是否有更改
  17. let attrs = diffAttr(oldNode.porps, newNode.props);
  18. if (Object.keys(attrs).length > 0) {
  19. currentPatch.push({
  20. type: ATTRS,
  21. attrs
  22. });
  23. }
  24. // 比较儿子们
  25. diffChildren(oldNode.children,newNode.children,patches);
  26. }else{
  27. // 说明节点被替换
  28. currentPatch.push({
  29. type: REPLACE,
  30. newNode
  31. });
  32. }
  33. currentPatch.length ? patches[index] = currentPatch : null;
  34. }
  35. function diffChildren(oldChildren,newChildren,patches) {
  36. oldChildren.forEach((child,ids)=>{
  37. // index 每次传递给walk时, index应该是递增的.所有的都基于同一个Index
  38. walk(child,newChildren[idx],++Index,patches);
  39. })
  40. }
  41. function diffAttr(oldAttrs, newAttrs) {
  42. let patch = {};
  43. // 判断老属性和新属性的关系
  44. for (let key in oldAttrs) {
  45. if (oldAttrs[key] !== newAttrs[key]) {
  46. patch[key] = newAttrs[key]; //有可能是undefined => 新节点中删了该属性
  47. }
  48. }
  49. // 新节点新增了很多属性
  50. for (let key in newAttrs) {
  51. if (!oldAttrs.hasOwnProperty(key)) {
  52. patch[key] = newAttrs[key];
  53. }
  54. }
  55. return patch;
  56. }

在diff过程中,我们需要去判断文本标签,需要在util中写一个工具函数

</>复制代码

  1. function isString(node) {
  2. return Object.prototype.toString.call(node)==="[object String]";
  3. }

实现思路非常简单,手工流程图了解下

通过diff后,最终我们会拿到新旧VDom的patches补丁,补丁的内容大致如下:

</>复制代码

  1. patches = {
  2. 1:{
  3. type:"REMOVE",
  4. index:1
  5. },
  6. 3:{
  7. type:"TEXT",
  8. newText:"hello Nealyang~",
  9. },
  10. 6:{
  11. type:"REPLACE",
  12. newNode:newNode
  13. }
  14. }

大致是这么个感觉,两秒钟体会下~

这里应该会有点诧异的是1 3 6...是什么鬼?

因为之前我们说过,diff采用的依旧是深度优先遍历,及时你是改良后的升级产品,但是遍历流程依旧是:

patches

既然patches补丁已经拿到了,该如何使用呢,对,我们依旧是遍历!

Element 调用render后,我们已经可以拿到一个通过VDom(代码)解析后的真是Dom了,所以我们只需要将遍历真实DOM,然后在指定位置修改对应的补丁上指定位置的更改就行了。

代码如下:(自己实现的简易版)

</>复制代码

  1. let allPaches = {};
  2. let index = 0; //默认哪个需要补丁
  3. export default function patch(dom, patches) {
  4. allPaches = patches;
  5. walk(dom);
  6. }
  7. function walk(dom) {
  8. let currentPatche = allPaches[index];
  9. let childNodes = dom.childNodes;
  10. childNodes.forEach(element => walk(element));
  11. if (currentPatche > 0) {
  12. doPatch(dom, currentPatche);
  13. }
  14. }
  15. function doPatch(node, patches) {
  16. patches.forEach(patch => {
  17. switch (patch.type) {
  18. case "ATTRS":
  19. setAttrs(patch.attrs)//别的文件方法
  20. break;
  21. case "TEXT":
  22. node.textContent = patch.text;
  23. break;
  24. case "REPLACE":
  25. let newNode = patch.newNode instanceof Element ? render(patch.newNode) : document.createTextNode(patch.newNode);
  26. node.parentNode.replaceChild(newNode, node)
  27. break;
  28. case "REMOVE":
  29. node.parentNode.removeChild(node);
  30. break;
  31. }
  32. })
  33. }

关于setAttrs其实功能都加都明白,这里给个简单实例代码,大家YY下

</>复制代码

  1. function setAttrs(dom, props) {
  2. const ALL_KEYS = Object.keys(props);
  3. ALL_KEYS.forEach(k =>{
  4. const v = props[k];
  5. // className
  6. if(k === "className"){
  7. dom.setAttribute("class",v);
  8. return;
  9. }
  10. if(k == "style") {
  11. if(typeof v == "string") {
  12. dom.style.cssText = v
  13. }
  14. if(typeof v == "object") {
  15. for (let i in v) {
  16. dom.style[i] = v[i]
  17. }
  18. }
  19. return
  20. }
  21. if(k[0] == "o" && k[1] == "n") {
  22. const capture = (k.indexOf("Capture") != -1)
  23. dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
  24. return
  25. }
  26. dom.setAttribute(k, v)
  27. })
  28. }

如上,其实我们已经实现了DOM diff了,但是存在一个问题.

如下图,老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。

针对这一现象,React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!

具体介绍可以参照 https://zhuanlan.zhihu.com/p/20346379

这里我们放到代码实现上:

</>复制代码

  1. /**
  2. * Diff two list in O(N).
  3. * @param {Array} oldList - Original List
  4. * @param {Array} newList - List After certain insertions, removes, or moves
  5. * @return {Object} - {moves: }
  6. * - moves is a list of actions that telling how to remove and insert
  7. */
  8. function diff (oldList, newList, key) {
  9. var oldMap = makeKeyIndexAndFree(oldList, key)
  10. var newMap = makeKeyIndexAndFree(newList, key)
  11. var newFree = newMap.free
  12. var oldKeyIndex = oldMap.keyIndex
  13. var newKeyIndex = newMap.keyIndex
  14. var moves = []
  15. // a simulate list to manipulate
  16. var children = []
  17. var i = 0
  18. var item
  19. var itemKey
  20. var freeIndex = 0
  21. // first pass to check item in old list: if it"s removed or not
  22. // 遍历旧的集合
  23. while (i < oldList.length) {
  24. item = oldList[i]
  25. itemKey = getItemKey(item, key)//itemKey a
  26. // 是否可以取到
  27. if (itemKey) {
  28. // 判断新集合中是否有这个属性,如果没有则push null
  29. if (!newKeyIndex.hasOwnProperty(itemKey)) {
  30. children.push(null)
  31. } else {
  32. // 如果有 去除在新列表中的位置
  33. var newItemIndex = newKeyIndex[itemKey]
  34. children.push(newList[newItemIndex])
  35. }
  36. } else {
  37. var freeItem = newFree[freeIndex++]
  38. children.push(freeItem || null)
  39. }
  40. i++
  41. }
  42. // children [{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
  43. var simulateList = children.slice(0)//[{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
  44. // remove items no longer exist
  45. i = 0
  46. while (i < simulateList.length) {
  47. if (simulateList[i] === null) {
  48. remove(i)
  49. removeSimulate(i)
  50. } else {
  51. i++
  52. }
  53. }
  54. // i is cursor pointing to a item in new list
  55. // j is cursor pointing to a item in simulateList
  56. var j = i = 0
  57. while (i < newList.length) {
  58. item = newList[i]
  59. itemKey = getItemKey(item, key)//c
  60. var simulateItem = simulateList[j] //{id:"a"}
  61. var simulateItemKey = getItemKey(simulateItem, key)//a
  62. if (simulateItem) {
  63. if (itemKey === simulateItemKey) {
  64. j++
  65. } else {
  66. // 新增项,直接插入
  67. if (!oldKeyIndex.hasOwnProperty(itemKey)) {
  68. insert(i, item)
  69. } else {
  70. // if remove current simulateItem make item in right place
  71. // then just remove it
  72. var nextItemKey = getItemKey(simulateList[j + 1], key)
  73. if (nextItemKey === itemKey) {
  74. remove(i)
  75. removeSimulate(j)
  76. j++ // after removing, current j is right, just jump to next one
  77. } else {
  78. // else insert item
  79. insert(i, item)
  80. }
  81. }
  82. }
  83. } else {
  84. insert(i, item)
  85. }
  86. i++
  87. }
  88. //if j is not remove to the end, remove all the rest item
  89. var k = simulateList.length - j
  90. while (j++ < simulateList.length) {
  91. k--
  92. remove(k + i)
  93. }
  94. // 记录旧的列表中移除项 {index:3,type:0}
  95. function remove (index) {
  96. var move = {index: index, type: 0}
  97. moves.push(move)
  98. }
  99. function insert (index, item) {
  100. var move = {index: index, item: item, type: 1}
  101. moves.push(move)
  102. }
  103. // 删除simulateList中null
  104. function removeSimulate (index) {
  105. simulateList.splice(index, 1)
  106. }
  107. return {
  108. moves: moves,
  109. children: children
  110. }
  111. }
  112. /**
  113. * Convert list to key-item keyIndex object.
  114. * 将列表转换为 key-item 的键值对象
  115. * [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}] -> [a:0,b:1,c:2...]
  116. * @param {Array} list
  117. * @param {String|Function} key
  118. */
  119. function makeKeyIndexAndFree (list, key) {
  120. var keyIndex = {}
  121. var free = []
  122. for (var i = 0, len = list.length; i < len; i++) {
  123. var item = list[i]
  124. var itemKey = getItemKey(item, key)
  125. if (itemKey) {
  126. keyIndex[itemKey] = i
  127. } else {
  128. free.push(item)
  129. }
  130. }
  131. return {
  132. keyIndex: keyIndex,
  133. free: free
  134. }
  135. }
  136. // 获取置顶key的value
  137. function getItemKey (item, key) {
  138. if (!item || !key) return void 666
  139. return typeof key === "string"
  140. ? item[key]
  141. : key(item)
  142. }
  143. exports.makeKeyIndexAndFree = makeKeyIndexAndFree
  144. exports.diffList = diff

代码参照:list-diff 具体的注释都已经加上。
使用如下:

</>复制代码

  1. import {diffList as diff} from "./lib/diffList";
  2. var oldList = [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}]
  3. var newList = [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
  4. var moves = diff(oldList, newList, "id")
  5. // type 0 表示移除, type 1 表示插入
  6. // moves: [
  7. // {index: 3, type: 0},
  8. // {index: 0, type: 1, item: {id: "c"}},
  9. // {index: 3, type: 0},
  10. // {index: 4, type: 1, item: {id: "f"}}
  11. // ]
  12. console.log(moves)
  13. moves.moves.forEach(function(move) {
  14. if (move.type === 0) {
  15. oldList.splice(move.index, 1) // type 0 is removing
  16. } else {
  17. oldList.splice(move.index, 0, move.item) // type 1 is inserting
  18. }
  19. })
  20. // now `oldList` is equal to `newList`
  21. // [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
  22. console.log(oldList)

这里我最困惑的地方时,实现diff都是index为索引,深度优先遍历,如果存在这种移动操作的话,那么之前我补丁patches里记录的index不就没有意义了么??

在 后来在开源的simple-virtual-dom中找到了index作为索引和标识去实现diff的答案。

第一点:在createElement的时候,去记录每一元素children的count数量

</>复制代码

  1. function Element(tagName, props, children) {
  2. if (!(this instanceof Element)) {
  3. if (!_.isArray(children) && children != null) {
  4. children = _.slice(arguments, 2).filter(_.truthy)
  5. }
  6. return new Element(tagName, props, children)
  7. }
  8. if (_.isArray(props)) {
  9. children = props
  10. props = {}
  11. }
  12. this.tagName = tagName
  13. this.props = props || {}
  14. this.children = children || []
  15. this.key = props ?
  16. props.key :
  17. void 666
  18. var count = 0
  19. _.each(this.children, function (child, i) {
  20. if (child instanceof Element) {
  21. count += child.count
  22. } else {
  23. children[i] = "" + child
  24. }
  25. count++
  26. })
  27. this.count = count
  28. }

第二点,在diff算法中,遇到移动的时候,我们需要及时更新我们全局变量index,核心代码`(leftNode && leftNode.count) ?
currentNodeIndex + leftNode.count + 1 :
currentNodeIndex + 1`。完整代码如下:

</>复制代码

  1. function diffChildren(oldChildren, newChildren, index, patches, currentPatch) {
  2. var diffs = diffList(oldChildren, newChildren, "key")
  3. newChildren = diffs.children
  4. if (diffs.moves.length) {
  5. var reorderPatch = {
  6. type: patch.REORDER,
  7. moves: diffs.moves
  8. }
  9. currentPatch.push(reorderPatch)
  10. }
  11. var leftNode = null
  12. var currentNodeIndex = index
  13. _.each(oldChildren, function (child, i) {
  14. var newChild = newChildren[i]
  15. currentNodeIndex = (leftNode && leftNode.count) ?
  16. currentNodeIndex + leftNode.count + 1 :
  17. currentNodeIndex + 1
  18. dfsWalk(child, newChild, currentNodeIndex, patches)
  19. leftNode = child
  20. })
  21. }

话说,这里困扰了我好久好久。。。。

回到开头

</>复制代码

  1. var REACT_ELEMENT_TYPE =
  2. (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) ||
  3. 0xeac7;

也就说明了这段代码的必要性。

0.3中diff的实现

最后我们在看下0.3中diff的实现:

</>复制代码

  1. updateMultiChild: function(nextChildren, transaction) {
  2. if (!nextChildren && !this._renderedChildren) {
  3. return;
  4. } else if (nextChildren && !this._renderedChildren) {
  5. this._renderedChildren = {}; // lazily allocate backing store with nothing
  6. } else if (!nextChildren && this._renderedChildren) {
  7. nextChildren = {};
  8. }
  9. var rootDomIdDot = this._rootNodeID + ".";
  10. var markupBuffer = null; // Accumulate adjacent new children markup.
  11. var numPendingInsert = 0; // How many root nodes are waiting in markupBuffer
  12. var loopDomIndex = 0; // Index of loop through new children.
  13. var curChildrenDOMIndex = 0; // See (Comment 1)
  14. for (var name in nextChildren) {
  15. if (!nextChildren.hasOwnProperty(name)) {continue;}
  16. // 获取当前节点与要渲染的节点
  17. var curChild = this._renderedChildren[name];
  18. var nextChild = nextChildren[name];
  19. // 是否两个节点都存在,且类型相同
  20. if (shouldManageExisting(curChild, nextChild)) {
  21. // 如果有插入标示,之后又循环到了不需要插入的节点,则直接插入,并把插入标示制空
  22. if (markupBuffer) {
  23. this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
  24. markupBuffer = null;
  25. }
  26. numPendingInsert = 0;
  27. // 如果找到当前要渲染的节点序号比最大序号小,则移动节点
  28. /*
  29. * 在0.3中,没有根据keydiff,而是通过Object中的key作为索引
  30. * 比如{a,b,c}替换成{c,b,c}
  31. * b._domIndex = 1挪到loopDomIndex = 1的位置,就是原地不动
  32. a._domIndex = 0挪到loopDomIndex = 2的位置,也就是和c换位
  33. */
  34. if (curChild._domIndex < curChildrenDOMIndex) { // (Comment 2)
  35. this.enqueueMove(curChild._domIndex, loopDomIndex);
  36. }
  37. curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
  38. // 递归更新子节点Props,调用子节点dom-diff...
  39. !nextChild.props.isStatic &&
  40. curChild.receiveProps(nextChild.props, transaction);
  41. curChild._domIndex = loopDomIndex;
  42. } else {
  43. // 当前存在,执行删除
  44. if (curChild) { // !shouldUpdate && curChild => delete
  45. this.enqueueUnmountChildByName(name, curChild);
  46. curChildrenDOMIndex =
  47. Math.max(curChild._domIndex, curChildrenDOMIndex);
  48. }
  49. // 当前不存在,下个节点存在, 执行插入,渲染下个节点
  50. if (nextChild) { // !shouldUpdate && nextChild => insert
  51. this._renderedChildren[name] = nextChild;
  52. // 渲染下个节点
  53. var nextMarkup =
  54. nextChild.mountComponent(rootDomIdDot + name, transaction);
  55. markupBuffer = markupBuffer ? markupBuffer + nextMarkup : nextMarkup;
  56. numPendingInsert++;
  57. nextChild._domIndex = loopDomIndex;
  58. }
  59. }
  60. loopDomIndex = nextChild ? loopDomIndex + 1 : loopDomIndex;
  61. }
  62. // 执行插入操作,插入位置计算方式如下:
  63. // 要渲染的节点位置-要插入的节点个数:比如当前要渲染的节点index=3,当前节点只有一个,也就是index=1
  64. // 如
    1
    渲染成
    1
    2
    3
  65. // 那么从
    2
    开始就开始加入buffer,最终buffer内容为
    2
    3
  66. // 那么要插入的位置为 3 - 1 = 2。我们以
    1
    为1,就是把buffer插入2的位置,也就是
    1
    后面
  67. if (markupBuffer) {
  68. this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
  69. }
  70. // 循环老节点
  71. for (var childName in this._renderedChildren) {
  72. if (!this._renderedChildren.hasOwnProperty(childName)) { continue; }
  73. var child = this._renderedChildren[childName];
  74. // 当前节点存在,下个节点不存在,删除
  75. if (child && !nextChildren[childName]) {
  76. this.enqueueUnmountChildByName(childName, child);
  77. }
  78. }
  79. // 一次提交所有操作
  80. this.processChildDOMOperationsQueue();
  81. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97504.html

相关文章

  • 浅谈React Fiber

    摘要:因为版本将真正废弃这三生命周期到目前为止,的渲染机制遵循同步渲染首次渲染,更新时更新时卸载时期间每个周期函数各司其职,输入输出都是可预测,一路下来很顺畅。通过进一步观察可以发现,预废弃的三个生命周期函数都发生在虚拟的构建期间,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段时间准备前端招聘事项...

    izhuhaodev 评论0 收藏0
  • React系列 --- virtualdom diff算法实现分析()

    摘要:所以只针对同层级节点做比较,将复杂度的问题转换成复杂度的问题。 React系列 React系列 --- 简单模拟语法(一)React系列 --- Jsx, 合成事件与Refs(二)React系列 --- virtualdom diff算法实现分析(三)React系列 --- 从Mixin到HOC再到HOOKS(四)React系列 --- createElement, ReactElem...

    sunsmell 评论0 收藏0
  • React 源码剖析系列 - 不可思议的 react diff

    摘要:目前,前端领域中势头正盛,使用者众多却少有能够深入剖析内部实现机制和原理。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。 目前,前端领域中 React 势头正盛,使用者众多却少有能够深入剖析内部实现机制和原理。本系列文章希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然。 React diff 作为 Virtual DOM 的加速...

    shuibo 评论0 收藏0
  • Deep In React之浅谈 React Fiber 架构(一)

    摘要:在上面我们已经知道浏览器是一帧一帧执行的,在两个执行帧之间,主线程通常会有一小段空闲时间,可以在这个空闲期调用空闲期回调,执行一些任务。另外由于这些堆栈是可以自己控制的,所以可以加入并发或者错误边界等功能。 文章首发于个人博客 前言 2016 年都已经透露出来的概念,这都 9102 年了,我才开始写 Fiber 的文章,表示惭愧呀。不过现在好的是关于 Fiber 的资料已经很丰富了,...

    Jiavan 评论0 收藏0
  • React前端学习小结

    摘要:正式开始系统地学习前端已经三个多月了,感觉前端知识体系庞杂但是又非常有趣。更新一个节点需要做的事情有两件,更新顶层标签的属性,更新这个标签包裹的子节点。 正式开始系统地学习前端已经三个多月了,感觉前端知识体系庞杂但是又非常有趣。前端演进到现在对开发人员的代码功底要求已经越来越高,几年前的前端开发还是大量操作DOM,直接与用户交互,而React、Vue等MVVM框架的出现,则帮助开发者从...

    iOS122 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<