ant.design 组件库中的 Tree 组件实现可搜索的树,在这里我会详细介绍每个方法,以及容易踩坑的点。
效果图:
首先是要导入的文件
js
// React 自带的属性
import React, { useMemo, useState } from 'react';
// antd 组件库中的,输入框和树形控件
import { Input, Tree } from 'antd';
// ts
import type { DataNode } from 'antd/es/tree';
下面是要渲染在 Tree 上的的数据,这是一个伪数据,如果你在开发时使用,直接修改给对应的变量名,赋值即可
js
const { Search } = Input;
const x = 3;
const y = 2;
const z = 1;
const defaultData: DataNode[] = [];
const generateData = (_level: number, _preKey?: React.Key, _tns?: DataNode[]) => {
const preKey = _preKey || '0';
const tns = _tns || defaultData;
const children: React.Key[] = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
这个方法是 Tree 组件提供的,用来筛选出要渲染的数据,筛选应该是优化一些性能的,因为在拿到接口数据时,每一项都可能会有很多的数据,而这一步是筛选出有用的值,过滤到其他,dataList 就是用来接收这个筛选后的数据的。
js
// 得到筛选后的值,根据当前要渲染的内容来筛选
const dataList: { key: React.Key; title: string }[] = [];
// 将筛选后的值,赋给 dataList
const generateList = (data: DataNode[]) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key as string });
if (node.children) {
generateList(node.children);
}
}
};
generateList(defaultData);
触发了搜索后,会使用这个方法来对比两个参数key 和 tree,得到符合条件的值
key:当前搜索的值
tree:所有渲染的数据
js
const getParentKey = (key: React.Key, tree: DataNode[]): React.Key => {
let parentKey: React.Key;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey!;
};
现在进入进入组件内,处理逻辑,以下内容都包裹在 App组件中
js
const App: React.FC = () => {}
export default App;
要使用到的可修改状态的值
expandedKeys :(受控)展开指定的树节点
searchValue : 在触发搜索时,存储当期输入的字段
autoExpandParent :是否自动展开父节点,所以它的默认值是布尔值
js
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [searchValue, setSearchValue] = useState('');
const [autoExpandParent, setAutoExpandParent] = useState(true);
每次树状图变动,会更新 expandedKeys ,来做接下来的操作
onExpand :展开/收起节点时触发
js
const onExpand = (newExpandedKeys: React.Key[]) => {
setExpandedKeys(newExpandedKeys);
setAutoExpandParent(false);
};
下面这段代码是一个事件处理函数onChange
,它接收一个React.ChangeEvent<HTMLInputElement>
类型的事件对象作为参数。这个事件函数通常用于处理输入框的值变化事件。
在函数内部,首先通过解构赋值取出事件对象的value
属性,即输入框的当前值。
然后,通过dataList
数组的map
方法遍历每个元素,并根据元素的title
属性是否包含输入框的值来判断是否展开节点。如果包含,则调用getParentKey
函数获取该节点的父节点的key
,否则返回null
。这个步骤用于更新展开的节点。
接下来,使用filter
方法对newExpandedKeys
数组进行过滤,去除其中的null
值,并且去掉重复的元素,得到最终的展开节点数组newExpandedKeys
。
调用setExpandedKeys
函数将最终的展开节点数组更新到状态中。
接着,通过setSearchValue
函数将输入框的值更新到状态中。
最后,调用setAutoExpandParent
函数将autoExpandParent
状态设置为true
,表示父节点也会被自动展开。
这段代码的作用是实现一个用于过滤和展开树节点的搜索功能。当输入框的值发生变化时,根据输入的值进行过滤,找到匹配的节点,并展开它们的父节点,同时更新输入框的值和展开节点的状态。
js
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
const newExpandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, defaultData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(newExpandedKeys as React.Key[]);
setSearchValue(value);
setAutoExpandParent(true);
};
下面这段代码使用了useMemo
钩子来创建一个名为treeData
的变量。useMemo
的作用是在依赖项(这里是searchValue
)发生变化时执行内部的函数,并将函数的返回值作为treeData
的值。
函数内部定义了一个名为loop
的递归函数,用于遍历树形结构的数据data
。
在loop
函数中,首先将item.title
转换为字符串类型,并使用indexOf
方法查找searchValue
在字符串中的位置。
根据索引的结果,通过substring
和slice
方法将字符串分割成前后两部分,然后构建一个新的title
元素。如果搜索值在标题中存在,使用<span>
元素将搜索值高亮显示;如果不存在,则直接使用原始的标题。
接着,判断item
是否有子节点(item.children
),如果有,则递归调用loop
函数处理子节点,并将返回的结果作为children
属性的值。
最后,在每个节点的处理中,都会返回一个包含title
和key
属性的对象。
最外层的useMemo
的返回值就是使用loop
函数处理defaultData
得到的结果,它代表了树形结构数据经过搜索值过滤和处理后的新数据。
总而言之,这段代码的作用是根据搜索值searchValue
对树形结构数据进行过滤,并对匹配的节点标题进行处理,添加搜索值的高亮显示。最后返回经过处理后的新的树形结构数据treeData
。
js
const treeData = useMemo(() => {
const loop = (data: DataNode[]): DataNode[] =>
data.map((item) => {
const strTitle = item.title as string;
const index = strTitle.indexOf(searchValue);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.slice(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{strTitle}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return loop(defaultData);
}, [searchValue]);
最后就是组件的结构了,这段代码中使用了 Search
组件,通过 placeholder
属性设置了一个提示文本 Search
,并通过 onChange
属性指定了一个事件处理函数 onChange
来处理搜索框的值变化事件。这个 onChange
函数通常用于更新状态或执行其他逻辑,以响应搜索框中输入的值的变化。
总而言之,这段代码创建了一个包含搜索框和树形结构的组件,并将搜索框的值变化和树节点的展开或折叠事件与相应的事件处理函数关联起来。
js
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} />
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={treeData}
/>
</div>
);
};
完整代码:
js
import React, { useMemo, useState } from 'react';
import { Input, Tree } from 'antd';
import type { DataNode } from 'antd/es/tree';
const { Search } = Input;
const x = 3;
const y = 2;
const z = 1;
const defaultData: DataNode[] = [];
const generateData = (_level: number, _preKey?: React.Key, _tns?: DataNode[]) => {
const preKey = _preKey || '0';
const tns = _tns || defaultData;
const children: React.Key[] = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
const dataList: { key: React.Key; title: string }[] = [];
const generateList = (data: DataNode[]) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key as string });
if (node.children) {
generateList(node.children);
}
}
};
generateList(defaultData);
const getParentKey = (key: React.Key, tree: DataNode[]): React.Key => {
let parentKey: React.Key;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey!;
};
const App: React.FC = () => {
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [searchValue, setSearchValue] = useState('');
const [autoExpandParent, setAutoExpandParent] = useState(true);
const onExpand = (newExpandedKeys: React.Key[]) => {
setExpandedKeys(newExpandedKeys);
setAutoExpandParent(false);
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
const newExpandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, defaultData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(newExpandedKeys as React.Key[]);
setSearchValue(value);
setAutoExpandParent(true);
};
const treeData = useMemo(() => {
const loop = (data: DataNode[]): DataNode[] =>
data.map((item) => {
const strTitle = item.title as string;
const index = strTitle.indexOf(searchValue);
const beforeStr = strTitle.substring(0, index);
const afterStr = strTitle.slice(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{strTitle}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key,
};
});
return loop(defaultData);
}, [searchValue]);
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} />
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={treeData}
/>
</div>
);
};
export default App;
官网地址: 树形控件 Tree - Ant Design