【react实现递归编辑树】

javascript 复制代码
import React, {useState} from 'react';
import {Input} from 'antd';
import {
  DeleteOutlined,
  PlusCircleOutlined,
  CaretDownOutlined,
} from '@ant-design/icons';

function findAndUpdateObjectById(id, arr, changeObj, type = '') {
  console.log('arguments', arguments)
  function findAndModify(currentArray) {
    for (let i = 0; i < currentArray.length; i++) {
      const currentItem = currentArray[i];

      if (currentItem.id === id) {
        const updatedItem = {...currentItem, ...changeObj};

        if (type === 'subtract') {
          return [
            ...currentArray.slice(0, i),
            ...currentArray.slice(i + 1)
          ];
        }

        if (changeObj.children && changeObj.children.length > 0) {
          return [
            ...currentArray.slice(0, i),
            {...currentItem, children: [...currentItem.children, ...changeObj.children]},
            ...currentArray.slice(i + 1)
          ];
        }

        return [
          ...currentArray.slice(0, i),
          updatedItem,
          ...currentArray.slice(i + 1)
        ];
      }

      if (currentItem.children && currentItem.children.length > 0) {
        // 这个result是改了传入id所在的children的数组对象,但是他的父元素里的children并没有改变
        const result = findAndModify(currentItem.children);
        if (result) {
          const updatedArray = [...currentArray];
          // 这里就是父的children也改变了
          updatedArray[i] = {...currentItem, children: result};
          return updatedArray;
        }
      }
    }
    return null;
  }

  return findAndModify(arr);
}

function Menu() {
  const [menuItems, setMenuItems] = useState([]);
  const [activeId, setActiveId] = useState(null);

  const handleAddMenuItem = (_, currentId: '') => {
    const newId = Date.now();
    const newMenuItem = {
      id: newId,
      editing: true,
      text: '菜单项' + newId,
      collapsed: true,
      children: [],
    };
    if (!!currentId) {
      setActiveId(newId);
      setMenuItems(prevItems => findAndUpdateObjectById(currentId, prevItems, {children: [newMenuItem]}))
      return;
    }
    setActiveId(newId);
    setMenuItems(prevItems => [...prevItems, newMenuItem]);
  };

  const handleDeleteMenuItem = (id) => {
    setMenuItems((prevItems) => findAndUpdateObjectById(id, prevItems, {}, 'subtract'))
  };

  const handleToggleEdit = (id) => {
    setMenuItems((prevItems) => findAndUpdateObjectById(id, prevItems, {editing: false}))
  };

  const handleChangeText = (id, newText) => {
    setMenuItems((prevItems) => findAndUpdateObjectById(id, prevItems, {text: newText}))
  };

  const handleMenuItemClick = (id) => {

      setMenuItems((prevItems) => findAndUpdateObjectById(id, prevItems, {editing: true}))
      setActiveId(id);
    }
  ;

  const handleMenuItemCollapsed = (e, item) => {
    const collapsed = item.collapsed;
    setMenuItems((prevItems) => findAndUpdateObjectById(item.id, prevItems, {collapsed: !collapsed}))
    setActiveId(item.id);
  }

  return (
    <div style={{width: '500px', height: '600px', backgroundColor: '#fff'}}>
      <button onClick={handleAddMenuItem}>新增</button>
      {menuItems.map(item => (
        <MenuItem
          key={item.id}
          item={item}
          onDelete={handleDeleteMenuItem}
          onToggleEdit={handleToggleEdit}
          onChangeText={handleChangeText}
          onClick={handleMenuItemClick}
          onAddChild={handleAddMenuItem}
          onCollapsed={handleMenuItemCollapsed}
          activeId={activeId}
        />
      ))}
    </div>
  );
}

function MenuItem({item, onAddChild, onDelete, onToggleEdit, onChangeText, onClick, activeId, onCollapsed}) {
  const handleDoubleClick = () => {
    onToggleEdit(item.id);
  };

  const handleChange = (e) => {
    e.stopPropagation(); // 阻止事件冒泡
    onChangeText(item.id, e.target.value);
  };

  const handleDelete = (e) => {
    e.stopPropagation(); // 阻止事件冒泡
    onDelete(item.id);
  };

  const handleClick = (e) => {
    e.stopPropagation(); // 阻止事件冒泡
    onClick(item.id);
  };

  const handleAdd = (e) => {
    e.stopPropagation(); // 阻止事件冒泡
    onAddChild(e, item.id)
  }

  const handleCollapsed = (e, item) => {
    e?.stopPropagation && e?.stopPropagation(); // 阻止事件冒泡
    onCollapsed(e, item)
  }

  return (
    <div>
      <div style={{display: 'flex'}}>
        {item.children.length > 0 && (
          <div
            style={{display: 'inline'}}
            onClick={(e) => handleCollapsed(e, item)}>
            <CaretDownOutlined rotate={item.collapsed ? 0 : -90}/>
          </div>)}
        {item.id === activeId && item.editing ? (
          <div style={{width: '480px'}}>
            <Input type="text" value={item.text} onChange={handleChange} onBlur={handleDoubleClick} autoFocus/>
          </div>
        ) : (
          <div style={{
            width: '500px',
            padding: '5px',
            backgroundColor: item.id === activeId ? 'rgb(218, 235, 254)' : '#fff',
            display: 'flex',
            justifyContent: 'space-between'
          }}
               onClick={handleClick}
               onDoubleClick={handleDoubleClick}>
            <span>{item.text}</span>
            <div style={{width: 40, display: 'flex', justifyContent: 'space-between'}}>
              <PlusCircleOutlined onClick={handleAdd}/>
              <DeleteOutlined onClick={handleDelete}/>
            </div>
          </div>
        )
        }
      </div>
      <div style={{marginLeft: item.children.length ? 20 : 0}}>
        {item.collapsed &&
        item.children.map((childItem) => (
          <MenuItem
            key={childItem.id}
            item={childItem}
            onDelete={onDelete}
            onToggleEdit={onToggleEdit}
            onChangeText={onChangeText}
            onClick={onClick}
            onAddChild={onAddChild}
            activeId={activeId}
            onCollapsed={handleCollapsed}
          />
        ))}
      </div>
    </div>

  );
}

export default Menu;
相关推荐
Revolution6114 小时前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
林焱_RPAAI14 小时前
影刀RPA技术深度:CSS选择器高级实战指南——伪类属性选择器与性能对比完全解析
vue.js·react.js
名字还没想好☜21 小时前
React 受控输入框光标跳到末尾:格式化输入时的 selection 丢失 bug 与修复
前端·javascript·react.js·bug·react·next.js
10share21 小时前
React 新一代样式隔离方案 —— 编译时、零运行时、原生写法
前端·react.js
光影少年1 天前
RN 的EventEmitter 双向通信
前端·react native·react.js
GuWenyue2 天前
90%前端写React+TS都踩坑!从组件类型、单向数据流到本地存储完整实战
前端·react.js
张元清2 天前
React useThrottle Hook:节流值与回调(2026)
javascript·react.js
烬羽2 天前
React 状态提升:一个 ColorPicker,彻底搞懂状态该放哪里
react.js·typescript·设计
console.log('npc')2 天前
React 跨项目集成实战:iframe 实现子项目详情弹窗
前端·javascript·react.js
无人生还2 天前
从 Vue3 到 React · 快速上手系列第 8 篇:组件通信与 Context(对标插槽与 provide/inject)
前端·vue.js·react.js