【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;
相关推荐
蓉妹妹2 小时前
React+Taro 微信小程序做一个页面,背景图需贴手机屏幕最上边覆盖展示
react.js·微信小程序·taro
进取星辰2 小时前
34、React Server Actions深度解析
前端·react.js·前端框架
程序员Bears10 小时前
React深度解析:Hooks体系与Redux Toolkit现代状态管理实践
前端·react.js·前端框架
进取星辰20 小时前
33、魔法防御术——React 19 安全攻防实战
前端·安全·react.js
懒人Ethan21 小时前
React TS中如何化简DOM事件的定义
javascript·react.js·ecmascript
JiaLin_Denny1 天前
react中运行 npm run dev 报错,提示vite.config.js出现错误 @esbuild/win32-x64
javascript·react.js·npm·esbuild·config.js·run dev
gong191723169671 天前
解释一下React事件系统中的事件委托机制
前端·javascript·react.js
JamSlade1 天前
React 个人笔记 Hooks编程
前端·javascript·笔记·react.js
开发者小天1 天前
React中使用 Ant Design Charts 图表
前端·javascript·react.js
爱吃小白兔的猫1 天前
从零开始创建React项目及制作页面
前端·react.js·前端框架