React虚拟DOM具体实现——利用节点json描述还原dom结构

  前两天,帮朋友解决一个问题:

   ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构。

  刚好最近在看React,并且了解到其中的虚拟DOM,其实,就是利用json数据来代替DOM结构表示,然后利用这个json数据,渲染出DOM树,总体添加到页面中。下面,我就通过介绍我如何实现上面实际问题的思路,一边完成实际需求,一边实现React中虚拟DOM渲染成DOM的原理。

  模拟数据结构如下:

 1 var allJson = [{
 2     ‘id‘: ‘1‘,
 3     ‘name‘: ‘我是1 我是根节点..我的长度是..‘
 4 }, {
 5     ‘id‘: ‘2‘,
 6     ‘parentId‘: ‘1‘,
 7     ‘name‘: ‘我是2 我的父级是1..我的长度是..‘
 8 }, {
 9     ‘id‘: ‘3‘,
10     ‘parentId‘: ‘2‘,
11     ‘name‘: ‘我是3 我的父级是2...我的长度是..‘
12 }, {
13     ‘id‘: ‘8‘,
14     ‘parentId‘: ‘4‘,
15     ‘name‘: ‘我是8 我的父级是4..我的长度是..‘
16 }, {
17     ‘id‘: ‘4‘,
18     ‘parentId‘: ‘2‘,
19     ‘name‘: ‘我是4 我的父级是2..我的长度是..‘
20 }, {
21     ‘id‘: ‘5‘,
22     ‘parentId‘: ‘3‘,
23     ‘name‘: ‘我是5 我的父级是3..我的长度是..‘
24 }, {
25     ‘id‘: ‘6‘,
26     ‘parentId‘: ‘1‘,
27     ‘name‘: ‘我是6 我的父级是1..我的长度是..‘
28 }, {
29     ‘id‘: ‘7‘,
30     ‘parentId‘: ‘4‘,
31     ‘name‘: ‘我是7 我的父级是4..我的长度是..‘
32 }];

方法一:直接将数据添加到页面中

  1)创建一个数组childRoot,用于存放已经添加到DOM树中的对象。(其中每一个元素,都可能有子节点),创建数组cloneAllJson,用于存放原始数据复制。

  2)利用根节点没有parentId,便利所有节点,找到根节点,并将该节点对象从cloneAllJson数组对象中移除(减少重复遍历)。

  3)循环childRoot数组,在数组剩下的节点对象中,根据parentId找到childRoot数组中的当前第一个元素的所有的子节点,每找到一个,直接添加到DOM树中,并添加到childRoot数组中,且从cloneAllJson数组中移除找到的子节点。添加完所有子节点后,移除childRoot的第一个元素。如此循环,直到childRoot数组长度为零。

代码如下:

 1 // 模拟数据
 2 var allJson = [{
 3     ‘id‘: ‘1‘,
 4     ‘name‘: ‘我是1 我是根节点..我的长度是..‘
 5 }, {
 6     ‘id‘: ‘2‘,
 7     ‘parentId‘: ‘1‘,
 8     ‘name‘: ‘我是2 我的父级是1..我的长度是..‘
 9 }, {
10     ‘id‘: ‘3‘,
11     ‘parentId‘: ‘2‘,
12     ‘name‘: ‘我是3 我的父级是2...我的长度是..‘
13 }, {
14     ‘id‘: ‘8‘,
15     ‘parentId‘: ‘4‘,
16     ‘name‘: ‘我是8 我的父级是4..我的长度是..‘
17 }, {
18     ‘id‘: ‘4‘,
19     ‘parentId‘: ‘2‘,
20     ‘name‘: ‘我是4 我的父级是2..我的长度是..‘
21 }, {
22     ‘id‘: ‘5‘,
23     ‘parentId‘: ‘3‘,
24     ‘name‘: ‘我是5 我的父级是3..我的长度是..‘
25 }, {
26     ‘id‘: ‘6‘,
27     ‘parentId‘: ‘1‘,
28     ‘name‘: ‘我是6 我的父级是1..我的长度是..‘
29 }, {
30     ‘id‘: ‘7‘,
31     ‘parentId‘: ‘4‘,
32     ‘name‘: ‘我是7 我的父级是4..我的长度是..‘
33 }, ];
34
35 /* 复制数据 */
36 var cloneAllJson = allJson.concat();
37
38 /* 找到根节点ID 并画出根节点到页面 */
39 /* 定义一个数组用来装每次新生成的次级父节点 */
40 var childRoot = [];
41 /* 遍历所有的接节点,查找根节点 */
42 for (var i = 0; i < allJson.length; i++) {
43     /* 如果不存在父节点字段 则为根节点 */
44     if (allJson[i].parentId == undefined) {
45         /* 赋值根节点ID */
46         rootId = allJson[i].id;
47         /* 将根节点添加到childRoot数组中,然后在复制的数组中删除这个根节点 */
48         childRoot.push(allJson[i]);
49         cloneAllJson.splice(i, 1);
50         /* 画出根节点 */
51         var div = document.createElement(‘div‘);
52         div.id = allJson[i].id;
53         div.appendChild(document.createTextNode(allJson[i].name));
54         document.getElementById(‘rootId‘).appendChild(div);
55     }
56 }
57
58
59 /*方法一:每次找到父节点的所有子节点,添加到dom树上,
60  *    并添加到childRoot数组中,从clone数组中剔除,(减少重复遍历)
61  *    直到childRoot数组中长度为0
62  *    可以解决,json数组中,父节点在子节点后面的问题
63  */
64 while (childRoot.length) {
65     /* 遍历cloneAllJson数组,找到childRoot第一个元素的所有子节点,并直接添加到页面上*/
66     for (var i = 0; i < cloneAllJson.length; i++) {
67         if (cloneAllJson[i].parentId == childRoot[0].id) {
68             /* 画出一级子节点 */
69             var div = document.createElement(‘div‘);
70             div.id = cloneAllJson[i].id;
71             div.appendChild(document.createTextNode(cloneAllJson[i].name));
72             /* 直接添加到页面上 */
73             document.getElementById(childRoot[0].id).appendChild(div);
74             /* 将该节点添加到childRoot中,之后遍历添加其子节点 */
75             childRoot.push(cloneAllJson[i]);
76             /* 将该节点从cloneAllJson数组中删除,并将索引向后减1 */
77             cloneAllJson.splice(i, 1);
78             i--;
79         }
80     }
81     /* 从childRoot数组中移除第一个元素(已经将其所有孩子添加到页面中) */
82     childRoot.shift();
83 }

最终生成dom结构如下图显示:

  其中rootId,是我们自己添加外节点。

  其实,在这个过程中,我强调了,每次找到节点,直接添加到页面上,在添加之前,都是先根据id查找父节点,其实,DOM操作性能很差,一般都是尽量减少DOM操作。在这里,我们就可以利用React中虚拟DOM渲染到页面上的方法了。

方法二:使用React虚拟DOM渲染方法

  其实主要思想还是方法一的思想,唯一不同,就是我们不是直接把节点对象添加到页面结构中,而是,给其父节点添加一个childObjs属性(用于存放所有子节点对象的数组)中。然后再利用递归,将所有节点渲染到页面上。其中,我们只进行了一次DOM查找操作,即最终调用render函数时

代码如下:

  1 /* 模拟数据 */
  2 var allJson = [{
  3     ‘id‘: ‘1‘,
  4     ‘name‘: ‘我是1 我是根节点..我的长度是..‘
  5 }, {
  6     ‘id‘: ‘2‘,
  7     ‘parentId‘: ‘1‘,
  8     ‘name‘: ‘我是2 我的父级是1..我的长度是..‘
  9 }, {
 10     ‘id‘: ‘3‘,
 11     ‘parentId‘: ‘2‘,
 12     ‘name‘: ‘我是3 我的父级是2...我的长度是..‘
 13 }, {
 14     ‘id‘: ‘8‘,
 15     ‘parentId‘: ‘4‘,
 16     ‘name‘: ‘我是8 我的父级是4..我的长度是..‘
 17 }, {
 18     ‘id‘: ‘4‘,
 19     ‘parentId‘: ‘2‘,
 20     ‘name‘: ‘我是4 我的父级是2..我的长度是..‘
 21 }, {
 22     ‘id‘: ‘5‘,
 23     ‘parentId‘: ‘3‘,
 24     ‘name‘: ‘我是5 我的父级是3..我的长度是..‘
 25 }, {
 26     ‘id‘: ‘6‘,
 27     ‘parentId‘: ‘1‘,
 28     ‘name‘: ‘我是6 我的父级是1..我的长度是..‘
 29 }, {
 30     ‘id‘: ‘7‘,
 31     ‘parentId‘: ‘4‘,
 32     ‘name‘: ‘我是7 我的父级是4..我的长度是..‘
 33 }];
 34
 35 /* 数据复制 */
 36 var cloneAllJson = allJson.concat();
 37
 38 /* 根节点对象 */
 39 var root = {};
 40 /* 定义一个数组用来装每次新生成的父节点 */
 41 var childRoot = [];
 42
 43 /* 查找根节点,并记录根节点,并将该节点从数组中剔除 */
 44 cloneAllJson.forEach(function(node, index) {
 45     /* 不存在parentId,就是根节点root */
 46     if (!node.parentId) {
 47         /* 引入深度,方便后期控制样式 */
 48         node.deep = 1;
 49         root = node;
 50         childRoot.push(root);
 51         cloneAllJson.splice(index, 1);
 52     }
 53 });
 54
 55 /* 给所有childRoot节点中childObj中添加其子节点 */
 56 while (childRoot.length) {
 57     let parent = childRoot[0];
 58     for (let j = 0; j < cloneAllJson.length; ++j) {
 59         let node = cloneAllJson[j];
 60         if (node.parentId == parent.id) {
 61             node.deep = parent.deep + 1;
 62             /* 引入childObjs,用于存放所有子节点对象的数组 */
 63             if (!parent.childObjs) {
 64                 parent.childObjs = [];
 65
 66             }
 67             parent.childObjs.push(node);
 68             childRoot.push(node);
 69             cloneAllJson.splice(j--, 1);
 70         }
 71     }
 72     childRoot.shift();
 73 }
 74
 75 console.log(root);
 76
 77 /* 渲染函数 */
 78 function render(node, root) {
 79     var elem;
 80     /* 如果节点存在子节点对象,创建该节点,并递归调用渲染函数,将其渲染为该节点的子元素 */
 81     /* 否则:直接渲染该节点*/
 82     if (node.childObjs) {
 83         var elem = createNode(node);
 84         node.childObjs.forEach(function(item) {
 85             render(item, elem);
 86         });
 87     } else {
 88         var elem = createNode(node);
 89     }
 90     /* 添加到页面中的节点上 */
 91     root.appendChild(elem);
 92 })
 93
 94 // 创建节点工厂函数
 95 function createNode(node) {
 96     var div = document.createElement(‘div‘);
 97     div.style.paddingLeft = 20 + ‘px‘;
 98     div.style.fontSize = 16 - node.deep + ‘px‘;
 99     div.appendChild(document.createTextNode(node.name));
100     return div;
101 }

最终显示结果截图:

  其实准确的说,我一共写了四种实现方法,但是这两种,是其中最好简单的两种,希望大家批评指正。

  

github上函数地址:https://github.com/DiligentYe/my-frame/blob/master/json-to-dom.js

时间: 03-17

React虚拟DOM具体实现——利用节点json描述还原dom结构的相关文章

React虚拟DOM浅析

转帖: http://www.alloyteam.com/2015/10/react-virtual-analysis-of-the-dom/?hmsr=toutiao.io&bsh_bid=928783684 在Web开发中,需要将数据的变化实时反映到UI上,这时就需要对DOM进行操作,但是复杂或频繁的DOM操作通常是性能瓶颈产生的原因,为此,React引入了虚拟DOM(Virtual DOM)的机制. 什么是虚拟DOM? 虚拟DOM VS 直接操作原生DOM? 虚拟DOM VS MVVM?

Javascript 动态添加节点(thinking in DOM)

test.html & example.js 文件说明了传统技术:document.write & innerHTML 的用法 test.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title> Test </title> </head> <body> <div id="testd

利用flask-sqlacodegen快速导入ORM表结构

利用flask-sqlacodegen快速导入ORM表结构 友情提示:如果是使用pymysql请预先pip install 哦~ 这是window下使用virtualenv环境下执行的 Linux用户可能使用起来不太一样 (env) d:\MyProject>flask-sqlacodegen --outfile models.py --flask mysql +pymysql://mysqlusername:[email protected]/dbname 几个有用的链接 https://py

轻松学习JavaScript二十二:DOM编程学习之节点操作

DOM编程不仅仅可以查找三种节点,也可以操作节点,那就是创建,插入,删除,替换和复制节点.先来看节点 操作方法: 还是借用一贯的HTML代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.

iOS中的数据解析(XML,JSON),SAX解析,DOM解析

第三方 SAT解析 #import "SAXTableViewController.h" #import "Student.h" @interface SAXTableViewController ()<NSXMLParserDelegate> @property (nonatomic, retain) NSMutableArray *dataSourse; // 存储学生对象 @property (nonatomic, retain) Student

DOM创建和删除节点

一.创建节点 3步 1.创建空元素对象: var newElem=document.createElement("标签名"); 例如:var a=document.createElement("a");//<a></a> 2.设置必要属性 newElem.属性名="值"; newElem.innerHTML="文本"; 例如:a.href="http://tmooc.cn";a.in

javascript获取dom的下一个节点方法

需求说明: 获取当前节点左节点或者右节点(兄弟节点): css: <style type="text/css"> a:focus { outline: none; } .panel { background: #69C7F7; height: 220px; display: none; } .slide { margin: 0; padding: 0; border-top: solid 4px #F27613; } .btn-slide { background: #F2

DOM读取和修改节点对象属性

一.获取和修改元素间的内容(3种) 1.innerHTML 获得/设置元素开始标签和结束标签之间的html原文 固定套路:1.删除父元素下所有子元素:parent.innerHTML=""; 2.批量替换父元素下所有子元素:parent.innerHTML="所有子元素标签组成的html" 2.textContent/innerText: 获得开始标签和结束标签之间的文本(去除标签) textContent 是DOM标准:innerText 是IE8: 3.文本节点

【JS学习笔记】DOM基础-首尾子节点,兄弟节点

一.DOM节点 (1)首尾子节点 有兼容性问题 firstChild.firstElementChild firstChild在高版本的浏览器上具有兼容问题,firstChild在高版本浏览器中指的是文本元素. firstElementChild高级浏览器下可以使用,在IE6-8下反而不兼容. 解决兼容性的办法是使用if进行判断 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://