我有一个基于输入对象绘制的树。我在顶部有一个搜索栏,基本上过滤掉了树的项目。我想在搜索时保持匹配项的打开状态。搜索结束后,树应恢复原状。理想情况下,整个树的搜索应该是完整的。当父节点是匹配时,它应该显示树直到那个级别(子节点可以被折叠)。但是当有匹配的子节点时,应该打开所有节点,直到找到匹配的节点
这是我尝试过的,https://codesandbox.io/s/modified-tree-final-ovgfv0?file=/src/index.js
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Tree from "./Tree";
import { data } from "./data";
import "./styles.css";
const TreeList = () => {
const [search, setSearch] = useState("");
return (
<>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<Tree data={data} />
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<TreeList />, rootElement);
首先,我要调整data
,使其格式从上到下保持一致。
export const data = [
{
key: "cat1",
label: "Category 1",
children: [
{
key: "1",
label: "Applications",
children: [
{
key: "3",
label: "Browser",
children: [
{
key: "4",
label: "Mozilla",
children: []
},
{
key: "5",
label: "Firefox",
children: []
}
]
}
]
}
]
},
{
key: "cat2",
label: "Category 2",
children: [
{
key: "1",
label: "Applications",
children: [
{
key: "2",
label: "OS",
children: [
{
key: "6",
label: "Windows",
children: []
}
]
}
]
}
]
}
];
这使得递归迭代更容易。
现在我们将改变索引JS,以便我们返回一个修改版本的树,每个节点都添加了属性,matched
和hasMatchedChildren
。我们还将根<TreeList>
的key
设置为每次键入时刷新整个树(包括用户可能更改的打开/折叠状态)。
import React, { useMemo, useState } from "react";
import ReactDOM from "react-dom";
import Tree from "./Tree";
import { data } from "./data";
import "./styles.css";
function searchTree(tree, query) {
function traverse(node, query) {
const matchedChildren = node.children.map((child) =>
traverse(child, query)
);
const isMatch = node.label.toLowerCase().includes(query.toLowerCase());
const hasMatchedChildren = matchedChildren.some(
(child) => child.matched || child.hasMatchedChildren
);
return {
...node,
children: matchedChildren,
matched: isMatch,
hasMatchedChildren: hasMatchedChildren
};
}
return tree.map((root) => traverse(root, query));
}
const TreeList = () => {
const [search, setSearch] = useState("");
const tree = useMemo(() => searchTree(data, search), [search]);
return (
<>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<Tree
key={search}
data={tree}
defaultCollapse={search === "" ? false : null}
/>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<TreeList />, rootElement);
Tree.js
现在被改变以反映稍微不同的格式:
import React from "react";
import TreeNode from "./TreeNode";
const Tree = ({ data = [], defaultCollapse }) => {
return (
<div className="d-tree">
<ul className="d-flex d-tree-container flex-column">
{data.map((node) => (
<TreeNode
key={node.key}
node={node}
defaultCollapse={defaultCollapse}
/>
))}
</ul>
</div>
);
};
export default Tree;
最后,也是最关键的是,TreeNode
被修改了,所以如果有匹配的子节点,默认的折叠状态为
import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const TreeNode = ({
node,
parentWasDirectMatch = false,
defaultCollapse,
level = 1
}) => {
const [childVisible, setChildVisiblity] = useState(
defaultCollapse !== null ? defaultCollapse : node.hasMatchedChildren
);
const hasChild = node.children && node.children.length > 0;
const nodeStyle = level === 1 ? { fontWeight: "bold", color: "red" } : {};
if (
((!hasChild && !node.matched) ||
(!node.matched && !node.hasMatchedChildren)) &&
!parentWasDirectMatch
) {
return null;
}
return (
<li className="d-tree-node border-0">
<div className="d-flex" onClick={(e) => setChildVisiblity((v) => !v)}>
{hasChild && (
<div
className={`d-inline d-tree-toggler ${
childVisible ? "active" : ""
}`}
>
<FontAwesomeIcon icon="caret-right" />
</div>
)}
<div className="col d-tree-head" style={nodeStyle}>
{node.label}
</div>
</div>
{hasChild && childVisible && (
<div className="d-tree-content">
<ul className="d-flex d-tree-container flex-column">
{node.children.map((item) => (
<TreeNode
key={item.key}
node={item}
level={level + 1}
parentWasDirectMatch={node.matched || parentWasDirectMatch}
defaultCollapse={defaultCollapse}
/>
))}
</ul>
</div>
)}
</li>
);
};
export default TreeNode;
Working codesandbox is here