React JS中基于UL/LI实现无外部依赖的可搜索树形视图(附JSON示例)
没问题!我帮你实现一个纯React的树形视图,自带搜索功能,完全用原生的ul/li标签,不需要任何外部依赖。下面是完整的实现方案:
纯React实现带搜索功能的树形视图(基于UL/LI,无外部库)
核心思路很简单:用递归组件渲染嵌套节点,通过状态管理搜索关键词和节点展开/折叠状态,同时实现智能过滤逻辑——只显示匹配的节点及其父节点。
1. 完整实现代码
import { useState, useMemo } from 'react'; // 你提供的示例JSON数据 const treeData = [ { value: "General", label: "General", count: "12 / 30" }, { value: "Accounting", label: "Accounting", count: "17 / 30" }, { value: "Labour Management", label: "Labour Management", count: "05 / 30", children: [ { value: "Labour ManagementChild", label: "Labour ManagementChild", children: [ { value: "Labour ManagementSub Child", label: "Labour ManagementSub Child" } ] }, { value: "Labour ManagementChild2", label: "Labour ManagementChild2" } ] } ]; const TreeView = () => { const [searchTerm, setSearchTerm] = useState(''); const [expandedNodes, setExpandedNodes] = useState(new Set()); // 切换节点展开/折叠状态 const toggleNode = (nodeValue) => { setExpandedNodes(prev => { const newSet = new Set(prev); newSet.has(nodeValue) ? newSet.delete(nodeValue) : newSet.add(nodeValue); return newSet; }); }; // 智能过滤树数据:保留匹配节点及所有父节点 const filteredTreeData = useMemo(() => { if (!searchTerm.trim()) return treeData; const filterNodes = (nodes) => { return nodes.reduce((acc, node) => { // 检查当前节点是否匹配搜索词 const isMatch = node.label.toLowerCase().includes(searchTerm.toLowerCase()); // 递归过滤子节点 const filteredChildren = node.children ? filterNodes(node.children) : []; // 当前节点匹配 或 有匹配的子节点,就保留该节点 if (isMatch || filteredChildren.length > 0) { acc.push({ ...node, children: filteredChildren.length > 0 ? filteredChildren : undefined }); } return acc; }, []); }; return filterNodes(treeData); }, [searchTerm]); return ( <div style={{ padding: '20px', maxWidth: '400px' }}> {/* 搜索输入框 */} <input type="text" placeholder="搜索节点..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} style={{ width: '100%', padding: '8px', marginBottom: '15px', borderRadius: '4px', border: '1px solid #ddd' }} /> {/* 树形视图容器 */} <ul style={{ listStyle: 'none', paddingLeft: '0', margin: '0' }}> {filteredTreeData.map(node => ( <TreeNode key={node.value} node={node} isExpanded={expandedNodes.has(node.value)} toggleNode={toggleNode} /> ))} </ul> </div> ); }; // 递归渲染的TreeNode组件 const TreeNode = ({ node, isExpanded, toggleNode }) => { const hasChildren = node.children && node.children.length > 0; return ( <li style={{ margin: '5px 0', paddingLeft: '15px', borderLeft: '1px solid #eee' }}> {/* 节点头部:展开按钮 + 标签 + 计数 */} <div style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', padding: '4px' }} onClick={() => hasChildren && toggleNode(node.value)} > {hasChildren && ( <span style={{ fontSize: '12px' }}> {isExpanded ? '▼' : '▶'} </span> )} <span>{node.label}</span> {node.count && ( <span style={{ marginLeft: 'auto', color: '#666', fontSize: '12px' }}> {node.count} </span> )} </div> {/* 渲染子节点(仅当展开且有子节点时) */} {isExpanded && hasChildren && ( <ul style={{ listStyle: 'none', paddingLeft: '10px', marginTop: '4px' }}> {node.children.map(childNode => ( <TreeNode key={childNode.value} node={childNode} isExpanded={false} // 子节点默认折叠,可按需改为记忆状态 toggleNode={toggleNode} /> ))} </ul> )} </li> ); }; export default TreeView;
2. 关键功能说明
搜索过滤逻辑
- 用
useMemo缓存过滤结果,避免不必要的重复计算 - 递归遍历节点时,会自动保留匹配节点的所有父节点,确保用户能看到匹配节点的层级关系
- 搜索不区分大小写,适配更友好的使用场景
展开/折叠功能
- 用
Set存储展开的节点value(保证唯一性),状态管理更高效 - 只有包含子节点的节点才会显示展开/折叠按钮,点击头部即可切换状态
样式自定义
- 用内联样式实现了基础的层级缩进和视觉区分,你可以轻松替换成自己的CSS类
- 节点左侧的边框强化了层级感,展开/折叠按钮用简单符号代替图标,无需额外资源
3. 使用方法
直接把这个TreeView组件导入到你的React项目中即可使用,不需要安装任何外部依赖。你可以根据需求调整:
- 搜索逻辑(比如支持搜索
value或count) - 展开/折叠的默认状态
- 整体样式风格
内容的提问来源于stack exchange,提问作者Nischal Tyagi
相关产品推荐
相关产品推荐

