当前位置: 首页 > 知识库问答 >
问题:

前端 - 使用antd的tree组件,搜索不到数据,请问这个是什么问题呢?

淳于乐池
2025-03-14

在使用:antd tree-demo-directory的时候,

我代码如下:

import React, { useMemo, useState } from 'react';
import { Input, Tree } from 'antd';
import type { TreeDataNode, TreeProps } from 'antd';
import './Left.css';
import { DownOutlined } from '@ant-design/icons';

const { Search } = Input;

// 预定义的静态树形数据
const treeData: TreeDataNode[] = [
  {
    title: 'parent 0',
    key: '0-0',
    children: [
      { title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
      { title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
    ],
  },
  {
    title: 'parent 1',
    key: '0-1',
    children: [
      { title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
      { title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
    ],
  },
];

// 获取父节点 Key
const getParentKey = (key: React.Key, tree: TreeDataNode[]): React.Key | null => {
  for (const node of tree) {
    if (node.children) {
      if (node.children.some((item) => item.key === key)) {
        return node.key;
      }
      const parentKey = getParentKey(key, node.children);
      if (parentKey !== null) {
        return parentKey;
      }
    }
  }
  return null;
};

export const Left: React.FC = () => {
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
  const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
  const [searchValue, setSearchValue] = useState('');
  const [autoExpandParent, setAutoExpandParent] = useState(true);

  // 搜索框变化事件
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    const newExpandedKeys = treeData
      .flatMap((item) => findMatchingKeys(item, value))
      .filter((key, i, self): key is React.Key => !!(key && self.indexOf(key) === i));
    setExpandedKeys(newExpandedKeys);
    setSearchValue(value);
    setAutoExpandParent(true);
  };

  const findMatchingKeys = (node: TreeDataNode, value: string): React.Key[] => {
    const matchingKeys: React.Key[] = [];
    const strTitle = String(node.title);

    // 如果当前节点匹配,添加当前节点的 key
    if (strTitle.toLowerCase().includes(value.toLowerCase())) {
      matchingKeys.push(node.key);

      // 添加父节点 key 以确保节点可见
      const parentKey = getParentKey(node.key, treeData);
      if (parentKey) {
        matchingKeys.push(parentKey);
      }
    }

    // 递归搜索子节点
    if (node.children) {
      node.children.forEach((child) => {
        matchingKeys.push(...findMatchingKeys(child, value));
      });
    }

    return matchingKeys;
  };
  // 动态生成过滤后的树数据
  const filteredTreeData = useMemo(() => {
    const loop = (data: TreeDataNode[]): TreeDataNode[] =>
      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 displayTitle =
            index > -1 ? (
              <span key={item.key}>
                {beforeStr}
                <span className="site-tree-search-value">{searchValue}</span>
                {afterStr}
              </span>
            ) : (
              <span key={item.key}>{strTitle}</span>
            );
          if (item.children) {
            return { title: strTitle, key: item.key, displayTitle, children: loop(item.children) };
          }
          return { title: strTitle, key: item.key, displayTitle };
        })
        .filter((item) => {
          const strTitle = item.title as string;
          return strTitle.toLowerCase().includes(searchValue.toLowerCase());
        });

    return loop(treeData);
  }, [searchValue, treeData]);

  // 勾选事件
  const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
    console.log('onCheck', checkedKeysValue);
    setCheckedKeys(checkedKeysValue as React.Key[]);
  };

  // 展开事件
  const onExpand: TreeProps['onExpand'] = (newExpandedKeys) => {
    console.log('onExpand', newExpandedKeys);
    setExpandedKeys(newExpandedKeys);
    setAutoExpandParent(false);
  };

  return (
    <div>
      <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} />
      <Tree
        showLine
        checkable
        switcherIcon={<DownOutlined />}
        onExpand={onExpand}
        expandedKeys={expandedKeys}
        autoExpandParent={autoExpandParent}
        onCheck={onCheck}
        checkedKeys={checkedKeys}
        treeData={filteredTreeData}
        titleRender={(nodeData) => nodeData.displayTitle}
      />
    </div>
  );
};

但是搜索是否有问题:数据搜索不到:
image.png

image.png

请问是什么原因导致呢?

共有2个答案

尹钱青
2025-03-14

修改 findMatchingKeys 函数

const findMatchingKeys = (node: TreeDataNode, value: string): React.Key[] => {
  const matchingKeys: React.Key[] = [];
  const strTitle = String(node.title);
  
  // 如果当前节点匹配,添加当前节点的 key
  if (strTitle.toLowerCase().includes(value.toLowerCase())) {
    matchingKeys.push(node.key);
    
    // 添加父节点 key 以确保节点可见
    const parentKey = getParentKey(node.key, treeData);
    if (parentKey) {
      matchingKeys.push(parentKey);
    }
  }
  
  // 递归搜索子节点
  if (node.children) {
    node.children.forEach((child) => {
      matchingKeys.push(...findMatchingKeys(child, value));
    });
  }
  
  return matchingKeys;
};

问题在于搜索逻辑有几个关键错误:

  • 没有正确添加匹配节点的 key:在 findMatchingKeys 函数中,只添加了父节点的 key,但没有添加匹配节点本身的 key。
const filteredTreeData = useMemo(() => {
  const loop = (data: TreeDataNode[]): TreeDataNode[] => {
    return data.map((item) => {
      const strTitle = String(item.title);
      const index = strTitle.toLowerCase().indexOf(searchValue.toLowerCase());
      const beforeStr = strTitle.substring(0, index);
      const afterStr = strTitle.slice(index + searchValue.length);
      
      const newItem: TreeDataNode = { ...item };
      
      if (index > -1) {
        // 匹配节点,创建高亮显示
        newItem.title = (
          <span>
            {beforeStr}
            <span className="site-tree-search-value">{strTitle.substr(index, searchValue.length)}</span>
            {afterStr}
          </span>
        );
      }
      
      // 处理子节点
      if (item.children) {
        newItem.children = loop(item.children);
        
        // 如果有匹配的子节点,即使父节点不匹配也要保留
        if (newItem.children.length > 0) {
          return newItem;
        }
      }
      
      // 返回匹配的节点
      return index > -1 ? newItem : (item.children ? newItem : null);
    }).filter(Boolean) as TreeDataNode[];
  };

  return searchValue ? loop(treeData) : treeData;
}, [searchValue]);
段干俊茂
2025-03-14

原因是:
过滤逻辑问题:在 filteredTreeData 中,过滤时只考虑了当前节点的 title,没有考虑子节点是否匹配。如果父节点不匹配但子节点匹配,父节点会被过滤掉,导致子节点也无法显示。

按照此可解决:

import React, { useMemo, useState } from 'react';
import { Input, Tree } from 'antd';
import type { TreeDataNode, TreeProps } from 'antd';
import { DownOutlined } from '@ant-design/icons';

import './Left.css'

const { Search } = Input;

// 预定义的静态树形数据
const treeData: TreeDataNode[] = [
  {
    title: 'parent 0',
    key: '0-0',
    children: [
      { title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
      { title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
    ],
  },
  {
    title: 'parent 1',
    key: '0-1',
    children: [
      { title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
      { title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
    ],
  },
];

// 获取父节点 Key
const getParentKey = (key: React.Key, tree: TreeDataNode[]): React.Key | null => {
  for (const node of tree) {
    if (node.children) {
      if (node.children.some((item) => item.key === key)) {
        return node.key;
      }
      const parentKey = getParentKey(key, node.children);
      if (parentKey !== null) {
        return parentKey;
      }
    }
  }
  return null;
};

export const Left: React.FC = () => {
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
  const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
  const [searchValue, setSearchValue] = useState('');
  const [autoExpandParent, setAutoExpandParent] = useState(true);

  // 搜索框变化事件
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    const newExpandedKeys = treeData
      .flatMap((item) => findMatchingKeys(item, value))
      .filter((key, i, self): key is React.Key => !!(key && self.indexOf(key) === i));
    setExpandedKeys(newExpandedKeys);
    setSearchValue(value);
    setAutoExpandParent(true);
  };

  const findMatchingKeys = (node: TreeDataNode, value: string): React.Key[] => {
    const matchingKeys: React.Key[] = [];
    const strTitle = String(node.title);

    // 如果当前节点匹配,添加当前节点的 key
    if (strTitle.toLowerCase().includes(value.toLowerCase())) {
      matchingKeys.push(node.key);

      // 添加父节点 key 以确保节点可见
      let parentKey = getParentKey(node.key, treeData);
      while (parentKey) {
        matchingKeys.push(parentKey);
        parentKey = getParentKey(parentKey, treeData);
      }
    }

    // 递归搜索子节点
    if (node.children) {
      node.children.forEach((child) => {
        matchingKeys.push(...findMatchingKeys(child, value));
      });
    }

    return matchingKeys;
  };

  // 动态生成过滤后的树数据
  const filteredTreeData = useMemo(() => {
    const loop = (data: TreeDataNode[]): TreeDataNode[] => {
      return data.filter((item) => {
        const strTitle = String(item.title);
        const isMatch = strTitle.toLowerCase().includes(searchValue.toLowerCase());
        const hasMatchingChildren = item.children && loop(item.children).length > 0;

        return isMatch || hasMatchingChildren;
      }).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 displayTitle =
          index > -1 ? (
            <span key={item.key}>
              {beforeStr}
              <span className="site-tree-search-value">{searchValue}</span>
              {afterStr}
            </span>
          ) : (
            <span key={item.key}>{strTitle}</span>
          );
        if (item.children) {
          const children = loop(item.children);
          return { title: strTitle, key: item.key, displayTitle, children };
        }
        return { title: strTitle, key: item.key, displayTitle };
      });
    };

    return loop(treeData);
  }, [searchValue, treeData]);

  // 勾选事件
  const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
    console.log('onCheck', checkedKeysValue);
    setCheckedKeys(checkedKeysValue as React.Key[]);
  };

  // 展开事件
  const onExpand: TreeProps['onExpand'] = (newExpandedKeys) => {
    console.log('onExpand', newExpandedKeys);
    setExpandedKeys(newExpandedKeys);
    setAutoExpandParent(false);
  };

  return (
    <div>
      <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} />
      <Tree
        showLine
        checkable
        switcherIcon={<DownOutlined />}
        onExpand={onExpand}
        expandedKeys={expandedKeys}
        autoExpandParent={autoExpandParent}
        onCheck={onCheck}
        checkedKeys={checkedKeys}
        treeData={filteredTreeData}
        titleRender={(nodeData) => nodeData.displayTitle}
      />
    </div>
  );
};
 类似资料: