【react实现递归编辑树】

在这里插入图片描述

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;