• 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

  • 相关阅读:
    【扩展】1. PHP 大括号{} 的使用
    preg_replace 中修正符 e 的解析
    terminal 修改终端显示的名字
    find 命令详解
    OSI 7层结构 粗认识
    vi 全解析
    awk 学习笔记
    scp 复制远程文件 文件带空格 处理
    更新博客地址啦!!!
    ubuntu16.04安装NVIDIA驱动遇到的问题
  • 原文地址:https://www.cnblogs.com/diligentYe/p/6580520.html
Copyright © 2020-2023  润新知