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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM