【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;
相关推荐
有意义15 小时前
从 useState 到 useEffect:React Hooks 核心机制详解
javascript·react.js·前端工程化
学高数就犯困15 小时前
React + Vite:用Fetch将.csv大文件数据转成JSON字符串
react.js
bytemanx17 小时前
深入源码:React 19 useActionState 与 Next.js Server Actions 的完美融合
react.js·next.js
ErMao19 小时前
开始搭建第一个React项目吧~
前端·react.js
苹果电脑的鑫鑫20 小时前
vue和react缩进规则的配置项如何配置
前端·vue.js·react.js
yuhaiqun198921 小时前
学AI Agent:从React模式到Plan框架,3条路径一次学透
人工智能·经验分享·笔记·react.js·机器学习·ai·aigc
程序员笨鸟1 天前
[特殊字符] React 高频 useEffect 导致页面崩溃的真实案例:从根因排查到彻底优化
前端·javascript·学习·react.js·面试·前端框架
普通网友1 天前
框架适配:React/Vue 项目中如何高效使用 debugger 断点
javascript·vue.js·react.js
Shriley_X1 天前
React
javascript·react.js·ecmascript
Highcharts.js1 天前
从旧版到新版:Highcharts for React 迁移全攻略 + 开发者必知的 5 大坑
前端·react.js·前端框架·编辑器·highcharts