0%

JS 筆記-不同陣列長度處理,並顯示於畫面

JavaScript Note

專案中遇到一個陣列方法綜合運用的情境,紀錄一下。
資料有兩個陣列,一個是 info、一個是 node,可以看到兩個是不同的長度。

情境

使用 Angular8 開發,因要用 D3.js 繪製流程圖,需求主要為 node 作為繪製流程的節點,並且使用 info 呈現目前節點狀態,若 info 沒有資料代表流程上為跑到,但節點要先顯示,有點像購物網站進度條的概念。

因有許多頁面需要使用到此資料處理邏輯,所以拉出來做共用元件,讓資料丟進來後可以傳給 D3.js 正確顯示。

資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
info = [
{
now_node: "T01",
node_action: "OK",
action_date: "2021-12-10",
memo: "test",
userName: "Tom",
},
{
now_node: "T02",
node_action: "OK",
action_date: "2021-12-10",
memo: "test",
userName: "Mary",
},
];

node = [
{
node: "T01",
children: ["T02"],
},
{
node: "T02",
children: ["T03"],
},
{
node: "T03",
children: ["T04"],
},
{
node: "T04",
children: ["T05"],
},
{
node: "T05",
children: ["T06"],
},
{
node: "T06",
children: ["T07"],
},
{
node: "T07",
children: ["T08"],
},
{
node: "T08",
children: ["T09"],
},
{
node: "T09",
children: ["T10"],
},
{
node: "T10",
children: [""],
},
];
  1. 命名兩個變數,分別取得資料內容(當作已經打 API 回來的狀態)
  2. newArr 是等等重組完要用的空陣列。
1
2
3
let node = this.node;
let info = this.info;
let newArr: any[] = [];

使用 find 找到屬性欄位

  1. 有提到 node 為主要節點,所以先用這個資料用 forEach 遍歷每一筆資料。

  2. 先找子節點,建立一個子節點的陣列,要來放等等子節點的資料。

    原因是陣列資料主節點為主要狀態節點,Children 屬性為下一個節點要去的值,這樣流程才會正確呈現。

  3. 子節點 item.children 跑 forEach 去找出所有的子節點屬性的值。

  4. 宣告一個 currentChild 子節點結果去儲存 find 的結果,使用 find 去找到 info 的節點 now_node 欄位是否有跟子節點 childrenNameNode 的值一樣,有的話就回傳 true。

find 方法會找到第一筆相符的條件後,回傳 true,透過 console.log() 去看會發現是一個物件型態。

1
2
3
4
5
6
7
8
node.forEach((item) => {
let childrenList: { node: string, status: string }[] = [];

item.children.forEach((childrenNodeName) => {
//*子節點
let currentChild = info.find((list) => list.now_node === childrenNodeName);
});
});
  1. 宣告一個 childStatus 子節點狀態的變數,並且判斷 currentChild 是 truthy 或是 falsy。若為 truthy,就使用 push 把資料放到 childrenList 的陣列中。
  2. 最後需求有規定一個物件格式,所以重新宣告一個 childNode 變數去儲存新的物件格式。

!! 兩個驚嘆號是快速讓變數變成布林值的寫法,等價於 new Boolean()。

1
2
3
4
5
6
7
let childStatus = !!currentChild ? currentChild.node_action : undefined;

let childNode = {
node: childrenNodeName,
status: childStatus ? childStatus : "",
};
childrenList.push(childNode);

以上使用了 forEachfindpush 三種方法以及 boolean 值轉換的方式。

  1. 主節點的資料整理方式與子節點相同。
1
2
3
4
5
6
7
8
9
10
11
let currentMain = info.find((main) => main.now_node === item.node);
let mainStatus = !!currentMain ? currentMain.node_action : undefined;

if (childrenList.length) {
let mainNode = {
node: item.node,
status: mainStatus ? mainStatus : "",
children: childrenList,
};
newArr.push(mainNode);
}

最後宣告一個要傳資料到 D3.js 的全域變數 nodeArr,其屬性為空陣列。

1
nodeArr: any[] = [];

並把剛剛組好的 newArr 賦值給 nodeArr,下面為節點取值的完整程式碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let node = this.node;
let info = this.info;
let newArr: any[] = [];
node.forEach((item) => {
let childrenList: { node: string, status: string }[] = [];

item.children.forEach((childrenNodeName) => {
//*子節點
let currentChild = info.find((list) => list.now_node === childrenNodeName);
let childStatus = !!currentChild ? currentChild.node_action : undefined;

let childNode = {
node: childrenNodeName,
status: childStatus ? childStatus : "",
};
childrenList.push(childNode);
});

//*主節點
let currentMain = info.find((main) => main.now_node === item.node);
let mainStatus = !!currentMain ? currentMain.node_action : undefined;

if (childrenList.length) {
let mainNode = {
node: item.node,
status: mainStatus ? mainStatus : "",
children: childrenList,
};
newArr.push(mainNode);
}
});
this.nodeArr = newArr;

組出已審核節點資訊

節點資訊有分成兩種,一種是流程已經跑到會出現資訊,另一個是流程還沒到,雖然有節點,但不會有節點資訊,有點像是購物車狀態,還沒進行到的部分會顯示該筆狀態,但不會有進度資訊。

  1. 宣告一個空陣列變數 infoList,並且用原本資料的 info 去跑 forEach,把每一筆資料的內容抓出來。
  2. 因需求也是要重組一個資訊內容,這邊在宣告一個空陣列 mainInfo,要放入得值可以看到是名稱與日期,所以我需要從 info 資料中取出這兩個資料,使用 push 方法放到 info 陣列中
  3. 宣告一個 nodeName 放節點名稱。
  4. 最後把 nodeInfo 的內容換成新組成的值。
  5. 使用 push 把 nodeInfo 物件依序放入 infoList 的陣列中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//* 主節點資訊
let infoList: any[] = [];
info.forEach((item) => {
let mainInfo = [];
info.push(item.userName);
info.push(item.action_date);
let nodeName = item.now_node;

this.nodeInfo = {
node: item.now_node,
node_name: nodeName ? nodeName : "",
info: info,
memo: item.memo ? item.memo : "",
};
infoList.push(this.nodeInfo);
});

組出未審核的節點資訊

  1. 宣告一個空陣列 nodeApplication 存放未審核的節點資訊。

  2. 因為我已經知道有部分節點是有資訊的,所以我用 filter 去過濾有節點的資料。

  3. 在宣告一個 infoList 變數去找出節點資訊如果跟過濾後的節點名稱一樣的時候就回傳 true。

    這邊發現如果沒有找到節點就會回傳 undefined

  4. 所以我就判斷當 infoList 是 undefined 的時候,就去組新的資訊內容。

  5. 最後再把資訊內容 push 到 nodeApplication 陣列中,這樣不管審核流程節點進行到哪裡都會有值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let nodeApplication: any[] = [];
this.node.filter((item) => {
let infoList = this.info.find((info) => info.now_node === item.node);

if (infoList === undefined) {
let nodeInfo = {
node: item.node,
node_name: item.node,
info: [],
memo: "",
};
nodeApplication.push(nodeInfo);
}
});

合併陣列

最後將已審核跟未審核的節點資訊使用 concat 的方法(這邊使用解構賦值的寫法),去賦值在全域的 info 的陣列中。

concat

1
this.info = infoList.concat(nodeApplication);

ES6

1
this.info = [...infoList, ...nodeApplication];

小結

以上就是使用陣列不同的方法整理資料,使用到 forEachpushfilterfindconcat 以及 ES6 解構賦值的寫法。算是透過此專案練習與更熟悉陣列與物件的操作方法。

完整程式碼

參考資料