You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中如何实现树形结构下点击按钮条件显示文本字段?

在React中实现树形文件夹结构的动态输入字段添加

核心思路

  1. 用状态维护树形数据、选中节点和输入框的显示状态
  2. 递归渲染树形结构,处理节点选中事件
  3. 点击new按钮时,定位到选中节点,触发其下方的输入框显示
  4. 输入完成后将新节点添加到树形数据中

步骤实现

1. 定义状态和初始树形数据

用useState管理树形数据、选中节点ID以及输入框的显示状态:

import { useState } from 'react';

const App = () => {
  // 初始树形数据,包含manufacturing文件夹
  const [treeData, setTreeData] = useState([
    {
      id: '1',
      name: 'Root',
      type: 'folder',
      children: [
        {
          id: '2',
          name: 'manufacturing',
          type: 'folder',
          children: [],
          isSelected: false,
          showInput: false
        },
        {
          id: '3',
          name: 'sales',
          type: 'folder',
          children: [],
          isSelected: false,
          showInput: false
        },
        {
          id: '4',
          name: 'report.pdf',
          type: 'file',
          isSelected: false,
          showInput: false
        }
      ],
      isSelected: false,
      showInput: false
    }
  ]);
  const [selectedNodeId, setSelectedNodeId] = useState(null);

2. 递归渲染树形节点

编写递归组件TreeItem,负责单个节点的渲染、选中事件处理,以及输入框的条件渲染:

const TreeItem = ({ node }) => {
    // 处理节点选中逻辑
    const handleNodeClick = () => {
      // 重置所有节点的选中状态
      const resetSelection = (data) => {
        return data.map(item => ({
          ...item,
          isSelected: false,
          children: item.children ? resetSelection(item.children) : []
        }));
      };
      const updatedData = resetSelection(treeData);
      // 标记当前节点为选中
      const setSelected = (data) => {
        return data.map(item => {
          if (item.id === node.id) {
            return { ...item, isSelected: true };
          }
          return { ...item, children: item.children ? setSelected(item.children) : [] };
        });
      };
      setTreeData(setSelected(updatedData));
      setSelectedNodeId(node.id);
    };

    // 处理输入框确认(新建节点)
    const handleInputConfirm = (e) => {
      if (e.key === 'Enter' && e.target.value.trim()) {
        const addNewNode = (data) => {
          return data.map(item => {
            if (item.id === node.id) {
              return {
                ...item,
                showInput: false,
                children: [
                  ...item.children,
                  {
                    id: Date.now().toString(),
                    name: e.target.value.trim(),
                    type: 'folder', // 可根据需求改为file
                    children: [],
                    isSelected: false,
                    showInput: false
                  }
                ]
              };
            }
            return { ...item, children: item.children ? addNewNode(item.children) : [] };
          });
        };
        setTreeData(addNewNode(treeData));
        e.target.value = '';
      }
    };

    return (
      <div style={{ marginLeft: '20px' }}>
        <div 
          onClick={handleNodeClick}
          style={{ 
            cursor: 'pointer', 
            fontWeight: node.isSelected ? 'bold' : 'normal',
            color: node.isSelected ? '#1890ff' : '#333'
          }}
        >
          {node.type === 'folder' ? '📁' : '📄'} {node.name}
        </div>
        {/* 条件渲染输入框 */}
        {node.showInput && (
          <input
            type="text"
            placeholder="输入名称..."
            autoFocus
            onKeyDown={handleInputConfirm}
            onBlur={() => {
              // 失去焦点隐藏输入框
              const hideInput = (data) => {
                return data.map(item => {
                  if (item.id === node.id) {
                    return { ...item, showInput: false };
                  }
                  return { ...item, children: item.children ? hideInput(item.children) : [] };
                });
              };
              setTreeData(hideInput(treeData));
            }}
            style={{ marginLeft: '20px', marginTop: '5px' }}
          />
        )}
        {/* 递归渲染子节点 */}
        {node.children && node.children.map(child => (
          <TreeItem key={child.id} node={child} />
        ))}
      </div>
    );
  };

3. 处理New按钮点击事件

点击右上角new按钮时,找到选中节点并开启输入框:

const handleNewClick = () => {
    if (!selectedNodeId) return; // 未选中节点时不执行

    const showInputForSelected = (data) => {
      return data.map(item => {
        if (item.id === selectedNodeId) {
          return { ...item, showInput: true };
        }
        return { ...item, children: item.children ? showInputForSelected(item.children) : [] };
      });
    };
    setTreeData(showInputForSelected(treeData));
  };

4. 组合页面结构

将树形组件和New按钮整合到页面中:

return (
    <div style={{ padding: '20px' }}>
      <div style={{ textAlign: 'right', marginBottom: '20px' }}>
        <button onClick={handleNewClick} style={{ padding: '8px 16px' }}>
          New
        </button>
      </div>
      {treeData.map(root => (
        <TreeItem key={root.id} node={root} />
      ))}
    </div>
  );
};

export default App;

关键说明

  • 选中节点时会自动重置其他节点的选中状态,确保同一时间只有一个节点被选中
  • 输入框支持按回车确认新建节点,失去焦点则自动隐藏
  • 可根据需求调整新建节点的类型(文件/文件夹),或修改输入框的样式与交互逻辑

内容的提问来源于stack exchange,提问作者Anuradha Karaguppi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 20:50:27