Ant Design-Tree

比較少會碰到的元件。

基本用法

basic

沒有任何功能,只單純顯示資料:

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
import { Tree } from 'antd'
import { DataNode } from 'antd/lib/tree'

const AntdTree = () => {
const treeData2: DataNode[] = [
{
title: 'A',
key: 'A',
children: [
{
title: 'A-1',
key: 'A-1'
},
{
title: 'A-2',
key: 'A-2'
}
]
},
{
title: 'B',
key: 'B',
children: [
{
title: 'B-1',
key: 'B-1'
},
{
title: 'B-2',
key: 'B-2'
}
]
}
]

return (
<div>
<Tree treeData={treeData2} defaultExpandedKeys={['B']} />
</div>
)
}

export default AntdTree

一個項目至少會有 titlekey 兩個 props,title 是顯示在畫面上的內容,key 則是拿來做操作時的一種參照。例如這邊一開始想讓 B 節點是展開的,就可以把 B 節點的 key 傳入 defaultExpandedKeys 就有這個效果了:

做成 Controlled Component

controlled-component

可以用 expandedKeys 設定展開的項目,再搭配 onExpand 來更新狀態就可以變成受控組件:

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
import { Tree } from 'antd'
import { DataNode } from 'antd/lib/tree'
import { useState } from 'react'

const AntdTree = () => {
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([])
const treeData2: DataNode[] = [
{
title: 'A',
key: 'A',
children: [
{
title: 'A-1',
key: 'A-1',
children: [
{
title: 'A-1-1',
key: 'A-1-1'
}
]
},
{
title: 'A-2',
key: 'A-2'
}
]
},
{
title: 'B',
key: 'B',
children: [
{
title: 'B-1',
key: 'B-1'
},
{
title: 'B-2',
key: 'B-2'
}
]
}
]

function handleExpand(expandedKeysValue: React.Key[]) {
console.log('expand keys', expandedKeysValue)
setExpandedKeys(expandedKeysValue)
}

return (
<div>
<Tree treeData={treeData2} expandedKeys={expandedKeys} onExpand={handleExpand} />
</div>
)
}

export default AntdTree

Draggable

draggable

要讓節點變成「可拖曳」項目的話要加上 draggable,並且在 onDrop 時來更新節點資料。

底下是一個簡單的範例,比較複雜的部分都是集中在 onDrop 的處理,所以請直接看註解會比較好理解一點:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { Tree } from 'antd'
import { DataNode, TreeProps } from 'antd/lib/tree'
import { useState } from 'react'

const AntdTree = () => {
const [treeData, setTreeData] = useState<DataNode[]>([
{
title: 'A',
key: 'A',
children: [
{
title: 'A-1',
key: 'A-1',
children: [
{
title: 'A-1-1',
key: 'A-1-1'
}
]
},
{
title: 'A-2',
key: 'A-2'
},
{
title: 'A-3',
key: 'A-3'
},
{
title: 'A-4',
key: 'A-4'
}
]
},
{
title: 'B',
key: 'B',
children: [
{
title: 'B-1',
key: 'B-1'
},
{
title: 'B-2',
key: 'B-2'
}
]
}
])

// onDrop 會傳入一個 info 參數,裡面可以拿到有用的資訊
const handleDrop: TreeProps['onDrop'] = (info) => {
// drag 元素的 key
const dragKey = info.dragNode.key
// drog 元素的 key
const dropKey = info.node.key
// drop 位置(不精確)
const dropPos = info.node.pos.split('-')
// 精確位置(用來判斷是不是頂層第一個位置)
const realDropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
// 是不是放到縫隙間
const isDropToGap = info.dropToGap

// 用來搜尋節點的 function
const searchNode = (
data: DataNode[],
key: React.Key,
callback: (nodeData: DataNode, nodeIndex: number, nodeArray: DataNode[]) => void
) => {
for (let i = 0; i < data.length; i++) {
const isMatchKey = data[i].key === key
if (isMatchKey) {
// 找到對應的 key,把資料和位置帶出去
return callback(data[i], i, data)
}
// 子節點存在,用遞迴搜尋
if (data[i].children) {
searchNode(data[i].children!, key, callback)
}
}
}

// 複製原本的資料
const newTreeData = JSON.parse(JSON.stringify(treeData))

let dragObj: DataNode

// 把 dragKey(被拖拉的節點)傳入,找出對應節點和位置
searchNode(newTreeData, dragKey, (nodeData, nodeIndex, nodeArray) => {
// 把被拖拉的節點從陣列中刪除
nodeArray.splice(nodeIndex, 1)
// 儲存被拖拉的節點資料
dragObj = nodeData
console.log('dragObj', dragObj)
})

// 如果不是插到中間
if (!isDropToGap) {
// 把 dropKey(目標節點)傳入,找出對應節點陣列(因為不是插入所以不需要位置)
searchNode(newTreeData, dropKey, (nodeData) => {
// 如果 drop 的節點陣列有 children 就用它的,沒有就建立一個新的(空陣列)
nodeData.children = nodeData.children || []
// 把 drag 節點放到最前面的位置
nodeData.children.unshift(dragObj)
})
}
// 如果是插到中間
else {
// 把 dropKey(目標節點)傳入,找出節點陣列和位置
searchNode(newTreeData, dropKey, (nodeData, nodeIndex, nodeArray) => {
console.log('nodeArray', nodeArray)
console.log('dropDataIndex', nodeIndex)
// -1 代表最頂層的第一個位置
if (realDropPosition === -1) {
// 放到目標節點前面
nodeArray.splice(nodeIndex, 0, dragObj)
} else {
// 放到目標節點後面
nodeArray.splice(nodeIndex + 1, 0, dragObj)
}
})
}

setTreeData(newTreeData)
}

return (
<div>
<Tree treeData={treeData} selectable={false} onDrop={handleDrop} draggable blockNode />
</div>
)
}

export default AntdTree

實際範例可以參考 CodeSandbox

Windows 終端配置 懶人包 11ty-Notes
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×