antd 的 Tree 控件沒有提供點擊展開的功能,只能通過左邊的三角形實現展開和收起,沒辦法只好自己實現這個功能。
先看效果
如圖實現的是類似 Mac 文件目錄形式的結構,有箭頭代表是個文件夾,點擊展開文件夾,點擊外層文件夾可以收起整個文件夾。
首先根據服務器返回的 Json 數據生成樹形結構
const data = {
name: "root",
children: [{
name: "a",
value: "/a",
children: [{
name: "file_1",
value: "/a/file_1"
}, {
name: "a_1",
value: "/a/a_1",
children: [{
name: "file_2",
value: "/a/a_1/file_2"
}, {
name: "file_3",
value: "/a/a_1/file_3"
}]
}, {
name: "a_2",
value: "/a/a_2",
children: [{
name: "file_4",
value: "/a/a_2/file_4"
}, {
name: "file_5",
value: "/a/a_2/file_5"
}]
}]
}, {
name: "b",
value: "/b",
children: [{
name: "b_1",
value: "/b/b_1",
children: [{
name: "file_6",
value: "/b/b_1/file_6"
}]
}]
}]
}
數據結構中有 children 字段代表是個文件夾,因此需要遞歸遍歷出 TreeNode 視圖
import React from "react";
import Tree from 'antd/lib/tree';
import 'antd/lib/tree/style/css';
const TreeNode = Tree.TreeNode;
class TreeView extends React.Component {
//遍歷json繪制出tree結構
mapData = (children) => {
if (children && Array.isArray(children)) {
return children.map((ele) => {
if (ele.children && Array.isArray(ele.children)) {
return <TreeNode title={ele.name} key={ele.value}>
{this.mapData(ele.children)}
</TreeNode>
} else {
return <TreeNode title={ele.name} key={ele.value}/>
}
})
}
return []
}
render() {
let content = []
let {name, children} = data
if (name) {
content.push(<TreeNode title={name} key="/">{this.mapData(children)}</TreeNode>)
}
return (
<Tree>
{content}
</Tree>
);
}
}
export default TreeView
已經完成第一步,生成樹形結構視圖。接下來就是要實現點擊展開樹形結構
state = {
expandedKeys: ["/"],
autoExpandParent: true,
selectedKeys: []
}
//選中的回調
onSelect = (selectedKeys, obj) => {
let {expandedKeys} = this.state
let selectedKey = this.state.selectedKeys
//選中的狀態
if (obj.selected) {
//判斷是否已經展開,未展開就添加到 expandedKeys 中
//已經展開就不用管
let index = expandedKeys.indexOf(selectedKeys[0])
if (index === -1) {
expandedKeys.push(selectedKeys[0])
this.setState({
expandedKeys: expandedKeys,
selectedKeys: selectedKeys
})
} else {
this.setState({
selectedKeys: selectedKeys
})
}
// 沒有 children 代表當前已沒有下一級目錄
if (obj.event && obj.selectedNodes.length === 1 && !obj.selectedNodes[0].props.children) {
//do something
}
} else {
//selectedKey 是上次選中的元素 在 expandedKeys 肯定是存在的
//找到下標后需要過濾掉子類目錄 例如上次選中的元素為 /a ,
//子類 /a/a_1 已經展開就需要從 expandedKeys 中移除這個元素
let index = expandedKeys.indexOf(selectedKey[0])
if (index !== -1) {
//過渡掉子類元素
expandedKeys = expandedKeys.filter((ele) => {
return !ele.includes(selectedKey[0])
})
this.setState({
expandedKeys: expandedKeys,
selectedKeys: []
})
} else {
this.setState({
selectedKeys: []
})
}
}
}
//展開的回調
onExpend = (expandedKey, obj) => {
let {expandedKeys} = this.state
//展開的狀態
if (obj.expanded) {
this.setState({
expandedKeys: expandedKey,
selectedKeys: []
})
} else {
//expandedKey 返回的是當前已經展開的元素 expandedKeys 是上次展開的元素
//比較兩個數組中缺少的元素得出當前被收起的是哪個元素
let removeArray = this.diffArray(expandedKey, expandedKeys)
//收起的時候需要把里面展開的元素一並移除,不然會造成收起動作無效
expandedKeys = expandedKeys.filter((ele) => {
return !ele.includes(removeArray[0])
})
this.setState({
expandedKeys: expandedKeys,
selectedKeys: []
})
}
}
//比較出2個數組中不一樣的元素
diffArray = (arr1, arr2) => {
let arr3 = [];
for (let i = 0; i < arr1.length; i++) {
if (arr2.indexOf(arr1[i]) === -1)
arr3.push(arr1[i]);
}
for (let j = 0; j < arr2.length; j++) {
if (arr1.indexOf(arr2[j]) === -1)
arr3.push(arr2[j]);
}
return arr3;
}
......
render() {
return (
<Tree
onExpand={this.onExpend}
expandedKeys={this.state.expandedKeys}
autoExpandParent={this.state.autoExpandParent}
onSelect={this.onSelect}
selectedKeys={this.state.selectedKeys}>
{content}
</Tree>
)
}
根index.js文件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
根APP組件
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import TreeView from "./TreeView";
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
</div>
<div style={{marginTop: "100px"}}>
<TreeView/>
</div>
</div>
);
}
}
export default App;