React虚拟DOM具体实现——利用节点json描述还原dom结构
2017-03-19 15:06
465 查看
前两天,帮朋友解决一个问题:
ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构。
刚好最近在看React,并且了解到其中的虚拟DOM,其实,就是利用json数据来代替DOM结构表示,然后利用这个json数据,渲染出DOM树,总体添加到页面中。下面,我就通过介绍我如何实现上面实际问题的思路,一边完成实际需求,一边实现React中虚拟DOM渲染成DOM的原理。
2)利用根节点没有parentId,便利所有节点,找到根节点,并将该节点对象从cloneAllJson数组对象中移除(减少重复遍历)。
3)循环childRoot数组,在数组剩下的节点对象中,根据parentId找到childRoot数组中的当前第一个元素的所有的子节点,每找到一个,直接添加到DOM树中,并添加到childRoot数组中,且从cloneAllJson数组中移除找到的子节点。添加完所有子节点后,移除childRoot的第一个元素。如此循环,直到childRoot数组长度为零。
代码如下:
最终生成dom结构如下图显示:
其中rootId,是我们自己添加外节点。
其实,在这个过程中,我强调了,每次找到节点,直接添加到页面上,在添加之前,都是先根据id查找父节点,其实,DOM操作性能很差,一般都是尽量减少DOM操作。在这里,我们就可以利用React中虚拟DOM渲染到页面上的方法了。
代码如下:
最终显示结果截图:
其实准确的说,我一共写了四种实现方法,但是这两种,是其中最好简单的两种,希望大家批评指正。
github上函数地址:https://github.com/DiligentYe/my-frame/blob/master/json-to-dom.js
ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构。
刚好最近在看React,并且了解到其中的虚拟DOM,其实,就是利用json数据来代替DOM结构表示,然后利用这个json数据,渲染出DOM树,总体添加到页面中。下面,我就通过介绍我如何实现上面实际问题的思路,一边完成实际需求,一边实现React中虚拟DOM渲染成DOM的原理。
模拟数据结构如下:
var allJson = [{ 'id': '1', 'name': '我是1 我是根节点..我的长度是..' }, { 'id': '2', 'parentId': '1', 'name': '我是2 我的父级是1..我的长度是..' }, { 'id': '3', 'parentId': '2', 'name': '我是3 我的父级是2...我的长度是..' }, { 'id': '8', 'parentId': '4', 'name': '我是8 我的父级是4..我的长度是..' }, { 'id': '4', 'parentId': '2', 'name': '我是4 我的父级是2..我的长度是..' }, { 'id': '5', 'parentId': '3', 'name': '我是5 我的父级是3..我的长度是..' }, { 'id': '6', 'parentId': '1', 'name': '我是6 我的父级是1..我的长度是..' }, { 'id': '7', 'parentId': '4', 'name': '我是7 我的父级是4..我的长度是..' }];
方法一:直接将数据添加到页面中
1)创建一个数组childRoot,用于存放已经添加到DOM树中的对象。(其中每一个元素,都可能有子节点),创建数组cloneAllJson,用于存放原始数据复制。2)利用根节点没有parentId,便利所有节点,找到根节点,并将该节点对象从cloneAllJson数组对象中移除(减少重复遍历)。
3)循环childRoot数组,在数组剩下的节点对象中,根据parentId找到childRoot数组中的当前第一个元素的所有的子节点,每找到一个,直接添加到DOM树中,并添加到childRoot数组中,且从cloneAllJson数组中移除找到的子节点。添加完所有子节点后,移除childRoot的第一个元素。如此循环,直到childRoot数组长度为零。
代码如下:
// 模拟数据 var allJson = [{ 'id': '1', 'name': '我是1 我是根节点..我的长度是..' }, { 'id': '2', 'parentId': '1', 'name': '我是2 我的父级是1..我的长度是..' }, { 'id': '3', 'parentId': '2', 'name': '我是3 我的父级是2...我的长度是..' }, { 'id': '8', 'parentId': '4', 'name': '我是8 我的父级是4..我的长度是..' }, { 'id': '4', 'parentId': '2', 'name': '我是4 我的父级是2..我的长度是..' }, { 'id': '5', 'parentId': '3', 'name': '我是5 我的父级是3..我的长度是..' }, { 'id': '6', 'parentId': '1', 'name': '我是6 我的父级是1..我的长度是..' }, { 'id': '7', 'parentId': '4', 'name': '我是7 我的父级是4..我的长度是..' }, ]; /* 复制数据 */ var cloneAllJson = allJson.concat(); /* 找到根节点ID 并画出根节点到页面 */ /* 定义一个数组用来装每次新生成的次级父节点 */ var childRoot = []; /* 遍历所有的接节点,查找根节点 */ for (var i = 0; i < allJson.length; i++) { /* 如果不存在父节点字段 则为根节点 */ if (allJson[i].parentId == undefined) { /* 赋值根节点ID */ rootId = allJson[i].id; /* 将根节点添加到childRoot数组中,然后在复制的数组中删除这个根节点 */ childRoot.push(allJson[i]); cloneAllJson.splice(i, 1); /* 画出根节点 */ var div = document.createElement('div'); div.id = allJson[i].id; div.appendChild(document.createTextNode(allJson[i].name)); document.getElementById('rootId').appendChild(div); } } /*方法一:每次找到父节点的所有子节点,添加到dom树上, * 并添加到childRoot数组中,从clone数组中剔除,(减少重复遍历) * 直到childRoot数组中长度为0 * 可以解决,json数组中,父节点在子节点后面的问题 */ while (childRoot.length) { /* 遍历cloneAllJson数组,找到childRoot第一个元素的所有子节点,并直接添加到页面上*/ for (var i = 0; i < cloneAllJson.length; i++) { if (cloneAllJson[i].parentId == childRoot[0].id) { /* 画出一级子节点 */ var div = document.createElement('div'); div.id = cloneAllJson[i].id; div.appendChild(document.createTextNode(cloneAllJson[i].name)); /* 直接添加到页面上 */ document.getElementById(childRoot[0].id).appendChild(div); /* 将该节点添加到childRoot中,之后遍历添加其子节点 */ childRoot.push(cloneAllJson[i]); /* 将该节点从cloneAllJson数组中删除,并将索引向后减1 */ cloneAllJson.splice(i, 1); i--; } } /* 从childRoot数组中移除第一个元素(已经将其所有孩子添加到页面中) */ childRoot.shift(); }
最终生成dom结构如下图显示:
其中rootId,是我们自己添加外节点。
其实,在这个过程中,我强调了,每次找到节点,直接添加到页面上,在添加之前,都是先根据id查找父节点,其实,DOM操作性能很差,一般都是尽量减少DOM操作。在这里,我们就可以利用React中虚拟DOM渲染到页面上的方法了。
方法二:使用React虚拟DOM渲染方法
其实主要思想还是方法一的思想,唯一不同,就是我们不是直接把节点对象添加到页面结构中,而是,给其父节点添加一个childObjs属性(用于存放所有子节点对象的数组)中。然后再利用递归,将所有节点渲染到页面上。其中,我们只进行了一次DOM查找操作,即最终调用render函数时。代码如下:
/* 模拟数据 */ var allJson = [{ 'id': '1', 'name': '我是1 我是根节点..我的长度是..' }, { 'id': '2', 'parentId': '1', 'name': '我是2 我的父级是1..我的长度是..' }, { 'id': '3', 'parentId': '2', 'name': '我是3 我的父级是2...我的长度是..' }, { 'id': '8', 'parentId': '4', 'name': '我是8 我的父级是4..我的长度是..' }, { 'id': '4', 'parentId': '2', 'name': '我是4 我的父级是2..我的长度是..' }, { 'id': '5', 'parentId': '3', 'name': '我是5 我的父级是3..我的长度是..' }, { 'id': '6', 'parentId': '1', 'name': '我是6 我的父级是1..我的长度是..' }, { 'id': '7', 'parentId': '4', 'name': '我是7 我的父级是4..我的长度是..' }]; /* 数据复制 */ var cloneAllJson = allJson.concat(); /* 根节点对象 */ var root = {}; /* 定义一个数组用来装每次新生成的父节点 */ var childRoot = []; /* 查找根节点,并记录根节点,并将该节点从数组中剔除 */ cloneAllJson.forEach(function(node, index) { /* 不存在parentId,就是根节点root */ if (!node.parentId) { /* 引入深度,方便后期控制样式 */ node.deep = 1; root = node; childRoot.push(root); cloneAllJson.splice(index, 1); } }); /* 给所有childRoot节点中childObj中添加其子节点 */ while (childRoot.length) { let parent = childRoot[0]; for (let j = 0; j < cloneAllJson.length; ++j) { let node = cloneAllJson[j]; if (node.parentId == parent.id) { node.deep = parent.deep + 1; /* 引入childObjs,用于存放所有子节点对象的数组 */ if (!parent.childObjs) { parent.childObjs = []; } parent.childObjs.push(node); childRoot.push(node); cloneAllJson.splice(j--, 1); } } childRoot.shift(); } console.log(root); /* 渲染函数 */ function render(node, root) { var elem; /* 如果节点存在子节点对象,创建该节点,并递归调用渲染函数,将其渲染为该节点的子元素 */ /* 否则:直接渲染该节点*/ if (node.childObjs) { var elem = createNode(node); node.childObjs.forEach(function(item) { render(item, elem); }); } else { var elem = createNode(node); } /* 添加到页面中的节点上 */ root.appendChild(elem); }) // 创建节点工厂函数 function createNode(node) { var div = document.createElement('div'); div.style.paddingLeft = 20 + 'px'; div.style.fontSize = 16 - node.deep + 'px'; div.appendChild(document.createTextNode(node.name)); return div; }
最终显示结果截图:
其实准确的说,我一共写了四种实现方法,但是这两种,是其中最好简单的两种,希望大家批评指正。
github上函数地址:https://github.com/DiligentYe/my-frame/blob/master/json-to-dom.js
相关文章推荐
- HDU2473 - Junk-Mail Filter 利用虚拟数组实现删除并查集的节点
- java递归实现json树结构,附带js实现树结构:子父节点
- 利用多叉树实现Ext JS中的无限级树形菜单(一种构建多级有序树形结构JSON的方法)
- 利用栈结构实现二叉树的非递归遍历,求二叉树深度、叶子节点数、两个结点的最近公共祖先及二叉树结点的最大距离
- IFC标准是为了满足建筑行业的信息交互与共享而产生的统一数据标准,是建 筑行业事实上的数据交换与共享标准。本文概要介绍了IFC标准的产生及发展 历程,IFC的整体框架结构,简要说明了IFC标准的实现方法和过程,描述了 当前的应用以及我们应该更加积极地利用IFC标准为建筑软件行业服务。
- 利用QtQuick 2.0(qml)实现叶子节点可以拖动的强大的树形结构
- 利用栈结构实现二叉树的非递归遍历,求二叉树深度、叶子节点数、两个结点的最近公共祖先及二叉树结点的最大距离
- 树的基本结构,以及利用链表实现树的各项操作(创建、添加/删除/打印树节点、销毁等等)
- 该文简要描述了DOM概念和内部逻辑结构,给出了DOM文档操作和XML文件互相转换java实现过程。
- React 虚拟dom是如何实现的
- proxy 利用get拦截,实现一个生成各种DOM节点的通用函数dom。
- 利用栈结构实现二叉树的非递归遍历,求二叉树深度、叶子节点数、两个结点的最近公共祖先及二叉树结点的最大距离
- 利用多叉树实现Ext JS中的无限级树形菜单(一种构建多级有序树形结构JSON的方法
- JS构建页面的DOM节点结构的实现代码
- JSP实现论坛树型结构的具体算法
- 利用T-SQL语句,实现数据库的备份与还原的功能
- 利用T-SQL语句,实现数据库的备份与还原的功能
- (精)如何利用T_SQL实现数据库备份与还原处理之一--(如何利用sql语句,正确备份数据库)
- (精)如何利用T_SQL实现数据库备份与还原处理之一--(如何利用sql语句,得到数据库文件目录)