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

如何在Material UI TreeView中实现可编辑树形节点?

实现Material UI TreeView的可编辑节点功能

我明白你遇到的问题了——直接把TextField作为TreeItem的label属性传入确实不会按预期生效,这是因为TreeItem内部会对传入的label做样式和结构上的封装,导致TextField的交互和显示都出问题。咱们可以通过管理节点状态+条件渲染的方式来实现可编辑的树形结构,下面是具体的解决方案:

核心思路

  1. 用状态管理每个节点的名称和编辑状态(是否处于可编辑模式)
  2. 给每个TreeItem的label做条件渲染:编辑状态显示TextField,非编辑状态显示节点名称(点击可切换到编辑模式)
  3. 处理TextField的回车、失焦事件,完成编辑后切换回非编辑状态

修改后的完整代码

import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import TreeView from '@material-ui/lab/TreeView';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import TreeItem from '@material-ui/lab/TreeItem';
import TextField from "@material-ui/core/TextField";

const useStyles = makeStyles({
  root: {
    height: 216,
  },
  editTextField: {
    width: '100%',
    '& input': {
      padding: '4px 8px',
    },
  },
});

export default function FileSystemNavigator() {
  const classes = useStyles();

  // 初始化节点状态:key为nodeId,value包含名称和编辑状态
  const [nodes, setNodes] = useState({
    "1": { name: "Application", isEditing: false },
    "2": { name: "Application", isEditing: false },
    "3": { name: "Chrome", isEditing: false },
    "4": { name: "Webstorm", isEditing: false },
    "5": { name: "Documents", isEditing: false },
    "6": { name: "Material-UI", isEditing: false },
    "7": { name: "src", isEditing: false },
    "8": { name: "index.js", isEditing: false },
    "9": { name: "tree-view.js", isEditing: false },
  });

  // 切换节点的编辑状态
  const toggleEditMode = (nodeId) => {
    setNodes(prev => ({
      ...prev,
      [nodeId]: { ...prev[nodeId], isEditing: !prev[nodeId].isEditing }
    }));
  };

  // 更新节点名称
  const updateNodeName = (nodeId, newName) => {
    setNodes(prev => ({
      ...prev,
      [nodeId]: { ...prev[nodeId], name: newName }
    }));
  };

  // 处理编辑完成(回车或失焦)
  const handleEditComplete = (nodeId) => {
    toggleEditMode(nodeId);
  };

  return (
    <TreeView 
      className={classes.root} 
      defaultCollapseIcon={<ExpandMoreIcon />} 
      defaultExpandIcon={<ChevronRightIcon />} 
      defaultExpanded={["1"]}
    >
      <TreeItem 
        nodeId="1" 
        label={
          nodes["1"].isEditing ? (
            <TextField
              className={classes.editTextField}
              value={nodes["1"].name}
              autoFocus
              onChange={(e) => updateNodeName("1", e.target.value)}
              onBlur={() => handleEditComplete("1")}
              onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("1")}
            />
          ) : (
            <span onClick={() => toggleEditMode("1")}>{nodes["1"].name}</span>
          )
        }
      >
        <TreeItem 
          nodeId="2" 
          label={
            nodes["2"].isEditing ? (
              <TextField
                className={classes.editTextField}
                value={nodes["2"].name}
                autoFocus
                onChange={(e) => updateNodeName("2", e.target.value)}
                onBlur={() => handleEditComplete("2")}
                onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("2")}
              />
            ) : (
              <span onClick={() => toggleEditMode("2")}>{nodes["2"].name}</span>
            )
          }
        />
        <TreeItem 
          nodeId="3" 
          label={
            nodes["3"].isEditing ? (
              <TextField
                className={classes.editTextField}
                value={nodes["3"].name}
                autoFocus
                onChange={(e) => updateNodeName("3", e.target.value)}
                onBlur={() => handleEditComplete("3")}
                onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("3")}
              />
            ) : (
              <span onClick={() => toggleEditMode("3")}>{nodes["3"].name}</span>
            )
          }
        />
        <TreeItem 
          nodeId="4" 
          label={
            nodes["4"].isEditing ? (
              <TextField
                className={classes.editTextField}
                value={nodes["4"].name}
                autoFocus
                onChange={(e) => updateNodeName("4", e.target.value)}
                onBlur={() => handleEditComplete("4")}
                onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("4")}
              />
            ) : (
              <span onClick={() => toggleEditMode("4")}>{nodes["4"].name}</span>
            )
          }
        />
      </TreeItem>
      <TreeItem 
        nodeId="5" 
        label={
          nodes["5"].isEditing ? (
            <TextField
              className={classes.editTextField}
              value={nodes["5"].name}
              autoFocus
              onChange={(e) => updateNodeName("5", e.target.value)}
              onBlur={() => handleEditComplete("5")}
              onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("5")}
            />
          ) : (
            <span onClick={() => toggleEditMode("5")}>{nodes["5"].name}</span>
          )
        }
      >
        <TreeItem 
          nodeId="6" 
          label={
            nodes["6"].isEditing ? (
              <TextField
                className={classes.editTextField}
                value={nodes["6"].name}
                autoFocus
                onChange={(e) => updateNodeName("6", e.target.value)}
                onBlur={() => handleEditComplete("6")}
                onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("6")}
              />
            ) : (
              <span onClick={() => toggleEditMode("6")}>{nodes["6"].name}</span>
            )
          }
        >
          <TreeItem 
            nodeId="7" 
            label={
              nodes["7"].isEditing ? (
                <TextField
                  className={classes.editTextField}
                  value={nodes["7"].name}
                  autoFocus
                  onChange={(e) => updateNodeName("7", e.target.value)}
                  onBlur={() => handleEditComplete("7")}
                  onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("7")}
                />
              ) : (
                <span onClick={() => toggleEditMode("7")}>{nodes["7"].name}</span>
              )
            }
          >
            <TreeItem 
              nodeId="8" 
              label={
                nodes["8"].isEditing ? (
                  <TextField
                    className={classes.editTextField}
                    value={nodes["8"].name}
                    autoFocus
                    onChange={(e) => updateNodeName("8", e.target.value)}
                    onBlur={() => handleEditComplete("8")}
                    onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("8")}
                  />
                ) : (
                  <span onClick={() => toggleEditMode("8")}>{nodes["8"].name}</span>
                )
              }
            />
            <TreeItem 
              nodeId="9" 
              label={
                nodes["9"].isEditing ? (
                  <TextField
                    className={classes.editTextField}
                    value={nodes["9"].name}
                    autoFocus
                    onChange={(e) => updateNodeName("9", e.target.value)}
                    onBlur={() => handleEditComplete("9")}
                    onKeyPress={(e) => e.key === 'Enter' && handleEditComplete("9")}
                  />
                ) : (
                  <span onClick={() => toggleEditMode("9")}>{nodes["9"].name}</span>
                )
              }
            />
          </TreeItem>
        </TreeItem>
      </TreeItem>
    </TreeView>
  );
}

关键细节说明

  • 状态管理:用useState维护每个节点的名称和编辑状态,确保每个节点的状态独立可控
  • 条件渲染:在TreeItem的label中,根据isEditing状态切换显示可点击的文本或可编辑的TextField
  • 交互优化:
    • 点击文本进入编辑模式,自动聚焦到TextField
    • 回车或失去焦点时完成编辑,切换回文本显示
    • 自定义editTextField类来调整输入框的样式,让它和TreeItem的布局更匹配
  • 为什么之前的代码无效:TreeItem的label属性如果直接传入TextField,会被内部的Typography组件包裹,导致TextField的样式和交互被干扰,无法正常编辑。通过条件渲染直接控制label的内容,就能避免这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:14:37