首页 > 其他分享 >React中编写操作树形数据的自定义Hook

React中编写操作树形数据的自定义Hook

时间:2023-07-12 09:11:58浏览次数:57  
标签:const 自定义 item React Hook key newNode data children

什么是 Hook

hook 即为钩子,是一种特殊的函数,它可以让你在函数式组件中使用一些 react 特性,目前在 react 中常用的 hook 有以下几类

  • useState: 用于在函数组件中定义和使用状态(state)。
  • useEffect:用于在函数组件中处理副作用,也可以模拟 react 生命周期
  • useContext:用于在函数组件中访问 React 的上下文(context)。
  • useCallback:用于在函数组件中缓存计算结果,避免无用的重复计算。
  • useMemo:用于在函数组件中缓存回调函数,避免无用的重渲染。

以上各种 hook 的用法在笔记文档中均有记录,如有兴趣可以前往阅览.

自定义 Hook

自定义 Hook 是指在 React 中编写的自定义函数,以便在各个组件之间重用逻辑。通过自定义 Hook,我们可以将一些逻辑抽象出来,使它们可以在不同的组件中共享和复用。

自定义 Hook 的命名以 “use” 开头,这是为了遵循 React 的 Hook 命名规范。自定义 Hook 可以使用任何 React 的内置 Hook,也可以组合其他自定义 Hook。

编写自定义 Hook

那么如何编写自定义 hook 呢,且看以下场景:

在 Antd 中有一个 Tree 组件,现在需要对 Tree 组件的数据进行操作来方便我们在 Tree 中插入,更新,上移,下移,删除节点,此时我们就可以编写一个自定义 hook 来统一操作类似于 TreeData 这样的树形数据

我们在此将这个 hook 函数其命名为 useTreeHandler,编写这个自定义 hook 函数只需要三步同时

  • 保存传入的数据
  • 为传入的数据编写操作函数
  • 将操作后的数据以及函数暴露出去供组件使用
const useTreeHandler = (TreeData: DataNode[]) => {
  const [gData, setGData] = useState(JSON.parse(JSON.stringify(TreeData)));
  return {
    gData,
  };
};

因为本次操作的是类似 Antd 中的树形数据,就暂且使用 DataNode 类型,当然这个类型可以根据我们的需要来设定或者写一个更加通用的类型
在此 hook 函数中我们要实现以下功能

  • insertNodeByKey: 根据 key 来插入子级节点
  • insertNodeInParentByKey: 根据 key 来插入同级节点
  • deleteNodeByKey: 根据 key 来删除当前节点
  • updateTreeDataByKey: 根据 key 来更新当前节点
  • moveNodeInTreeByKey: 根据 key 上移/下移当前节点

插入子级

/**
 * 插入子级
 * @param key 当前节点key
 * @param newNode 待插入节点
 */
const insertNodeByKey = function (
  key: string | number | undefined,
  newNode: any
) {
  const data = JSON.parse(JSON.stringify(gData));
  const insertChild = (
    data: any[],
    key: string | number | undefined,
    newNode: any
  ): any[] => {
    for (let i = 0; i < data.length; i++) {
      if (data[i].key === key) {
        if (Array.isArray(data[i].children)) {
          data[i].children = [...data[i].children, newNode];
        } else {
          data[i].children = [newNode];
        }
        break;
      } else if (Array.isArray(data[i].children)) {
        insertChild(data[i].children, key, newNode);
      }
    }
    return data;
  };
  setGData(insertChild(data, key, newNode));
};

上述insertNodeByKey函数代码中传入了两个参数keynewNode,这两个分别代表当前操作节点对象的 key 以及插入的新节点数据,在insertNodeByKey函数内部对 gData 进行了一次深拷贝,之后在函数内操作深拷贝之后的数据,接着又定义了一个inserChild函数此函数主要进行数据操作,最后将操作后的数据重新赋值给 gData,在inserChild函数中首先对数组数据进行循环遍历,检查每一项的 key 是否和目标 key 相同,如果相同的话将新节点数据插入到当前遍历的节点的children中并break跳出循环,没有找到的话进行递归.
接下来更新节点,删除节点,上移/下移的函数和插入节点函数思路相同,在此就不一一解释,如下直接贴上代码:

插入同级

/**
 * 插入同级
 * @param key 当前节点key 供查询父key
 * @param newNode 新节点数据
 */
const insertNodeInParentByKey = function (
  key: string | number | undefined,
  newNode: any
) {
  const data = JSON.parse(JSON.stringify(gData));
  const insertBro = (
    data: any[],
    key: string | number | undefined,
    newNode: any
  ) => {
    for (let i = 0; i < data.length; i++) {
      const item = data[i];
      if (item.children) {
        for (let j = 0; j < item.children.length; j++) {
          const childItem = item.children[j];
          if (childItem.key === key) {
            item.children.push(newNode);
            break;
          } else if (childItem.children) {
            insertBro([childItem], key, newNode);
          }
        }
      }
    }
    return data;
  };
  setGData(insertBro(data, key, newNode));
};

删除当前节点

/**
 * 删除当前节点
 * @param data 源数据
 * @param key 待删除节点key
 */
const deleteNodeByKey = function (key: string | number | undefined) {
  const data = JSON.parse(JSON.stringify(gData));
  const delNode = (data: any[], key: string | number | undefined) => {
    for (let i = 0; i < data.length; i++) {
      const obj = data[i];
      if (obj.key === key) {
        data.splice(i, 1);
        break;
      } else if (obj.children) {
        delNode(obj.children, key);
        if (obj.children.length === 0) {
          delete obj.children;
        }
      }
    }
  };
  delNode(data, key);
  setGData(data);
};

更新当前节点

/**
 * 更新子节点配置
 * @param oldData 旧数据
 * @param key 待更新子节点key
 * @param newData 更新后新数据
 */
const updateTreeDataByKey = function (
  key: string | number | undefined,
  newData: any
) {
  const data = JSON.parse(JSON.stringify(gData));
  const updateNode = (
    oldData: any[],
    key: string | number | undefined,
    newData: any[]
  ) => {
    for (let i = 0; i < oldData.length; i++) {
      if (oldData[i].key === key) {
        oldData[i] = { ...oldData[i], ...newData };
        break;
      } else {
        if (Array.isArray(oldData[i].children)) {
          updateNode(oldData[i].children, key, newData);
        }
      }
    }
  };
  updateNode(data, key, newData);
  setGData(data);
};

当前节点上移/下移

/**
 * 上移/下移
 * @param data 源数据
 * @param key 目标key
 * @param direction 移动类型
 * @returns 更新后数据
 */
const moveNodeInTreeByKey = function (
  key: string | number | undefined,
  direction: "UP" | "DOWN"
) {
  const data = JSON.parse(JSON.stringify(gData));
  const moveNode = (
    data: any[],
    key: string | number | undefined,
    direction: string
  ) => {
    const newData = [...data];
    for (let i = 0; i < newData.length; i++) {
      const item = newData[i];
      const itemLen = item.children.length;
      if (item.children) {
        for (let j = 0; j < itemLen; j++) {
          const childItem = item.children[j];
          if (childItem.key === key) {
            if (j === 0 && direction === "UP")
              // message.info("已经处于第一位,无法上移");
              message.info({
                content: "已经处于第一位,无法上移",
                className: "custom-class",
                style: {
                  marginTop: "5vh",
                  position: "absolute",
                  right: 20,
                  textAlign: "center",
                },
              });
            if (j === itemLen - 1 && direction === "DOWN")
              // message.info("已经处于最后一位,无法下移");
              message.info({
                content: "已经处于最后一位,无法下移",
                className: "custom-class",
                style: {
                  marginTop: "5vh",
                  position: "absolute",
                  right: 20,
                  textAlign: "center",
                },
              });
            // splice (开始位置,移除元素个数,新增元素对象)
            if (direction === "UP") {
              item.children.splice(j, 1);
              item.children.splice(j - 1, 0, childItem);
            } else {
              item.children.splice(j, 1);
              item.children.splice(j + 1, 0, childItem);
            }

            break;
          } else if (childItem.children) {
            moveNode([childItem], key, direction);
          }
        }
      }
    }
    return newData;
  };
  setGData(moveNode(data, key, direction));
};

完整的 hook 函数

const useTreeHandler = (TreeData: DataNode[]) => {
  const [gData, setGData] = useState(JSON.parse(JSON.stringify(TreeData)));
  /**
   * 插入子级
   * @param key 当前节点key
   * @param newNode 待插入节点
   */
  const insertNodeByKey = function (
    key: string | number | undefined,
    newNode: any
  ) {
    const data = JSON.parse(JSON.stringify(gData));
    const insertChild = (
      data: any[],
      key: string | number | undefined,
      newNode: any
    ): any[] => {
      for (let i = 0; i < data.length; i++) {
        if (data[i].key === key) {
          if (Array.isArray(data[i].children)) {
            data[i].children = [...data[i].children, newNode];
          } else {
            data[i].children = [newNode];
          }
          break;
        } else if (Array.isArray(data[i].children)) {
          insertChild(data[i].children, key, newNode);
        }
      }
      return data;
    };
    setGData(insertChild(data, key, newNode));
  };

  /**
   * 插入同级
   * @param key 当前节点key 供查询父key
   * @param newNode 新节点数据
   */
  const insertNodeInParentByKey = function (
    key: string | number | undefined,
    newNode: any
  ) {
    const data = JSON.parse(JSON.stringify(gData));
    const insertBro = (
      data: any[],
      key: string | number | undefined,
      newNode: any
    ) => {
      for (let i = 0; i < data.length; i++) {
        const item = data[i];
        if (item.children) {
          for (let j = 0; j < item.children.length; j++) {
            const childItem = item.children[j];
            if (childItem.key === key) {
              item.children.push(newNode);
              break;
            } else if (childItem.children) {
              insertBro([childItem], key, newNode);
            }
          }
        }
      }
      return data;
    };
    setGData(insertBro(data, key, newNode));
  };
  /**
   * 删除当前节点
   * @param data 源数据
   * @param key 待删除节点key
   */
  const deleteNodeByKey = function (key: string | number | undefined) {
    const data = JSON.parse(JSON.stringify(gData));
    const delNode = (data: any[], key: string | number | undefined) => {
      for (let i = 0; i < data.length; i++) {
        const obj = data[i];
        if (obj.key === key) {
          data.splice(i, 1);
          break;
        } else if (obj.children) {
          delNode(obj.children, key);
          if (obj.children.length === 0) {
            delete obj.children;
          }
        }
      }
    };
    delNode(data, key);
    setGData(data);
  };
  /**
   * 更新子节点配置
   * @param oldData 旧数据
   * @param key 待更新子节点key
   * @param newData 更新后新数据
   */
  const updateTreeDataByKey = function (
    key: string | number | undefined,
    newData: any
  ) {
    const data = JSON.parse(JSON.stringify(gData));
    const updateNode = (
      oldData: any[],
      key: string | number | undefined,
      newData: any[]
    ) => {
      for (let i = 0; i < oldData.length; i++) {
        if (oldData[i].key === key) {
          oldData[i] = { ...oldData[i], ...newData };
          break;
        } else {
          if (Array.isArray(oldData[i].children)) {
            updateNode(oldData[i].children, key, newData);
          }
        }
      }
    };
    updateNode(data, key, newData);
    setGData(data);
  };
  /**
   * 上移/下移
   * @param data 源数据
   * @param key 目标key
   * @param direction 移动类型
   */
  const moveNodeInTreeByKey = function (
    key: string | number | undefined,
    direction: "UP" | "DOWN"
  ) {
    const data = JSON.parse(JSON.stringify(gData));
    const moveNode = (
      data: any[],
      key: string | number | undefined,
      direction: string
    ) => {
      const newData = [...data];
      for (let i = 0; i < newData.length; i++) {
        const item = newData[i];
        const itemLen = item.children.length;
        if (item.children) {
          for (let j = 0; j < itemLen; j++) {
            const childItem = item.children[j];
            if (childItem.key === key) {
              if (j === 0 && direction === "UP")
                message.info("已经处于第一位,无法上移");
              if (j === itemLen - 1 && direction === "DOWN")
                message.info("已经处于最后一位,无法下移");
              // splice (开始位置,移除元素个数,新增元素对象)
              if (direction === "UP") {
                item.children.splice(j, 1);
                item.children.splice(j - 1, 0, childItem);
              } else {
                item.children.splice(j, 1);
                item.children.splice(j + 1, 0, childItem);
              }

              break;
            } else if (childItem.children) {
              moveNode([childItem], key, direction);
            }
          }
        }
      }
      return newData;
    };
    setGData(moveNode(data, key, direction));
  };
  return {
    gData,
    insertNodeByKey,
    insertNodeInParentByKey,
    deleteNodeByKey,
    updateTreeDataByKey,
    moveNodeInTreeByKey,
  };
};

写在最后

演示地址

完整代码

标签:const,自定义,item,React,Hook,key,newNode,data,children
From: https://www.cnblogs.com/plumliil/p/17546554.html

相关文章

  • shallowReactive 与 shallowRef
    shallowReactive:只处理对象最外层属性的响应式(浅响应式)。shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。什么时候使用?如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive。如果有一个对象数据,后续功能不会修改该对......
  • mapbox添加自定义控件
    需要定义一个类,然后至少重写实现onAdd、onRemove方法,示例如下<template><divref="changeViewRef"@click="changeView"class="changeViewmapboxgl-ctrl"><el-tooltipclass="box-item"effect="dark"......
  • 自定义筛选AutoFilter
    AutoFilter语法:expression.AutoFilterVBA直接输入这个是在自动筛选和关闭来回切换。AutoFilter.FilterMode属性如果工作表处于自动筛选筛选器模式,则返回 True。只读 Boolean。表达 一个代表 AutoFilter 对象的变量。语法:expression.FilterModee.g:SubClearFilter()......
  • VBA自定义排序
    SortField.clear方法清除所有 SortFields 对象。SortFields.Add方法创建新的排序字段,并返回一个SortFields 对象。语法:expression.SortFields.add(key、SortOn、 Order、 CustomOrder、 DataOption)'Key:指定排序字段的范围或单元格。'SortOn:指定排序方式。例如xl......
  • SignalR 外部调用自定义Hub类的方法,Clients为null
    这是因为外部调用的类的对象和你连接的Hub类的对象,这两个对象不!一!样!解决方法在自定义的Hub类中,注入IHubContext对象,然后在方法中调用IHubContext对象来向前端推送数据publicclassDataHub:AbpCommonHub,ITransientDependency{publicIOnlineClientManag......
  • frida hook工具使用——用于os api注入分析还是不错的
    准备:pipinstallfridapipinstallfrida-tools 开始:1、创建child-gating1.pyimportosimportthreadingfromfrida_tools.applicationimportReactorimportfridaimportargparseclassApplication:def__init__(self,log_location):self.fpat......
  • 自定义参数类型断言装饰器
    代码frominspectimportsignaturefromfunctoolsimportwrapsdeftypeassert(*ty_args,**ty_kwargs):defdecorate(func):ifnot__debug__:returnfuncsig=signature(func)#获取函数签名bound_types=sig.bind_parti......
  • 前端框架及项目面试-聚焦Vue、React、Webpack
    第1章课程导学介绍课程制作的背景和课程主要内容。第2章课程介绍先出几道面试真题,引导思考。带着问题来继续学习,效果更好。第3章Vue使用Vue是前端面试必考内容,首先要保证自己要会使用Vue。本章讲解Vue基本使用、组件使用、高级特性和VuexVue-router,这些部分的知识点和......
  • arcgis pro自定义ribbon
     参考:https://pro.arcgis.com/en/pro-app/2.9/get-started/customize-the-ribbon.htm......
  • React18+Next.js13+TS,B端+C端完整业务+技术双闭环(20章)
    最新React技术栈,实战复杂低代码项目-仿问卷星第1章开期准备试看3节|20分钟介绍课程内容,学习建议和注意事项。演示课程项目,让学员有一个整体的认识。第2章【入门】什么是ReactReact引领了现代前端开发的变革8节|50分钟介绍React的历史、背景和每次版本更新。介绍R......