经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » React » 查看文章
React中编写操作树形数据的自定义Hook
来源:cnblogs  作者:Plumliil  时间:2023/7/12 10:57:43  对本文有异议

什么是 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 函数只需要三步同时

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

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

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

插入子级

  1. /**
  2. * 插入子级
  3. * @param key 当前节点key
  4. * @param newNode 待插入节点
  5. */
  6. const insertNodeByKey = function (
  7. key: string | number | undefined,
  8. newNode: any
  9. ) {
  10. const data = JSON.parse(JSON.stringify(gData));
  11. const insertChild = (
  12. data: any[],
  13. key: string | number | undefined,
  14. newNode: any
  15. ): any[] => {
  16. for (let i = 0; i < data.length; i++) {
  17. if (data[i].key === key) {
  18. if (Array.isArray(data[i].children)) {
  19. data[i].children = [...data[i].children, newNode];
  20. } else {
  21. data[i].children = [newNode];
  22. }
  23. break;
  24. } else if (Array.isArray(data[i].children)) {
  25. insertChild(data[i].children, key, newNode);
  26. }
  27. }
  28. return data;
  29. };
  30. setGData(insertChild(data, key, newNode));
  31. };

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

插入同级

  1. /**
  2. * 插入同级
  3. * @param key 当前节点key 供查询父key
  4. * @param newNode 新节点数据
  5. */
  6. const insertNodeInParentByKey = function (
  7. key: string | number | undefined,
  8. newNode: any
  9. ) {
  10. const data = JSON.parse(JSON.stringify(gData));
  11. const insertBro = (
  12. data: any[],
  13. key: string | number | undefined,
  14. newNode: any
  15. ) => {
  16. for (let i = 0; i < data.length; i++) {
  17. const item = data[i];
  18. if (item.children) {
  19. for (let j = 0; j < item.children.length; j++) {
  20. const childItem = item.children[j];
  21. if (childItem.key === key) {
  22. item.children.push(newNode);
  23. break;
  24. } else if (childItem.children) {
  25. insertBro([childItem], key, newNode);
  26. }
  27. }
  28. }
  29. }
  30. return data;
  31. };
  32. setGData(insertBro(data, key, newNode));
  33. };

删除当前节点

  1. /**
  2. * 删除当前节点
  3. * @param data 源数据
  4. * @param key 待删除节点key
  5. */
  6. const deleteNodeByKey = function (key: string | number | undefined) {
  7. const data = JSON.parse(JSON.stringify(gData));
  8. const delNode = (data: any[], key: string | number | undefined) => {
  9. for (let i = 0; i < data.length; i++) {
  10. const obj = data[i];
  11. if (obj.key === key) {
  12. data.splice(i, 1);
  13. break;
  14. } else if (obj.children) {
  15. delNode(obj.children, key);
  16. if (obj.children.length === 0) {
  17. delete obj.children;
  18. }
  19. }
  20. }
  21. };
  22. delNode(data, key);
  23. setGData(data);
  24. };

更新当前节点

  1. /**
  2. * 更新子节点配置
  3. * @param oldData 旧数据
  4. * @param key 待更新子节点key
  5. * @param newData 更新后新数据
  6. */
  7. const updateTreeDataByKey = function (
  8. key: string | number | undefined,
  9. newData: any
  10. ) {
  11. const data = JSON.parse(JSON.stringify(gData));
  12. const updateNode = (
  13. oldData: any[],
  14. key: string | number | undefined,
  15. newData: any[]
  16. ) => {
  17. for (let i = 0; i < oldData.length; i++) {
  18. if (oldData[i].key === key) {
  19. oldData[i] = { ...oldData[i], ...newData };
  20. break;
  21. } else {
  22. if (Array.isArray(oldData[i].children)) {
  23. updateNode(oldData[i].children, key, newData);
  24. }
  25. }
  26. }
  27. };
  28. updateNode(data, key, newData);
  29. setGData(data);
  30. };

当前节点上移/下移

  1. /**
  2. * 上移/下移
  3. * @param data 源数据
  4. * @param key 目标key
  5. * @param direction 移动类型
  6. * @returns 更新后数据
  7. */
  8. const moveNodeInTreeByKey = function (
  9. key: string | number | undefined,
  10. direction: "UP" | "DOWN"
  11. ) {
  12. const data = JSON.parse(JSON.stringify(gData));
  13. const moveNode = (
  14. data: any[],
  15. key: string | number | undefined,
  16. direction: string
  17. ) => {
  18. const newData = [...data];
  19. for (let i = 0; i < newData.length; i++) {
  20. const item = newData[i];
  21. const itemLen = item.children.length;
  22. if (item.children) {
  23. for (let j = 0; j < itemLen; j++) {
  24. const childItem = item.children[j];
  25. if (childItem.key === key) {
  26. if (j === 0 && direction === "UP")
  27. // message.info("已经处于第一位,无法上移");
  28. message.info({
  29. content: "已经处于第一位,无法上移",
  30. className: "custom-class",
  31. style: {
  32. marginTop: "5vh",
  33. position: "absolute",
  34. right: 20,
  35. textAlign: "center",
  36. },
  37. });
  38. if (j === itemLen - 1 && direction === "DOWN")
  39. // message.info("已经处于最后一位,无法下移");
  40. message.info({
  41. content: "已经处于最后一位,无法下移",
  42. className: "custom-class",
  43. style: {
  44. marginTop: "5vh",
  45. position: "absolute",
  46. right: 20,
  47. textAlign: "center",
  48. },
  49. });
  50. // splice (开始位置,移除元素个数,新增元素对象)
  51. if (direction === "UP") {
  52. item.children.splice(j, 1);
  53. item.children.splice(j - 1, 0, childItem);
  54. } else {
  55. item.children.splice(j, 1);
  56. item.children.splice(j + 1, 0, childItem);
  57. }
  58. break;
  59. } else if (childItem.children) {
  60. moveNode([childItem], key, direction);
  61. }
  62. }
  63. }
  64. }
  65. return newData;
  66. };
  67. setGData(moveNode(data, key, direction));
  68. };

完整的 hook 函数

  1. const useTreeHandler = (TreeData: DataNode[]) => {
  2. const [gData, setGData] = useState(JSON.parse(JSON.stringify(TreeData)));
  3. /**
  4. * 插入子级
  5. * @param key 当前节点key
  6. * @param newNode 待插入节点
  7. */
  8. const insertNodeByKey = function (
  9. key: string | number | undefined,
  10. newNode: any
  11. ) {
  12. const data = JSON.parse(JSON.stringify(gData));
  13. const insertChild = (
  14. data: any[],
  15. key: string | number | undefined,
  16. newNode: any
  17. ): any[] => {
  18. for (let i = 0; i < data.length; i++) {
  19. if (data[i].key === key) {
  20. if (Array.isArray(data[i].children)) {
  21. data[i].children = [...data[i].children, newNode];
  22. } else {
  23. data[i].children = [newNode];
  24. }
  25. break;
  26. } else if (Array.isArray(data[i].children)) {
  27. insertChild(data[i].children, key, newNode);
  28. }
  29. }
  30. return data;
  31. };
  32. setGData(insertChild(data, key, newNode));
  33. };
  34. /**
  35. * 插入同级
  36. * @param key 当前节点key 供查询父key
  37. * @param newNode 新节点数据
  38. */
  39. const insertNodeInParentByKey = function (
  40. key: string | number | undefined,
  41. newNode: any
  42. ) {
  43. const data = JSON.parse(JSON.stringify(gData));
  44. const insertBro = (
  45. data: any[],
  46. key: string | number | undefined,
  47. newNode: any
  48. ) => {
  49. for (let i = 0; i < data.length; i++) {
  50. const item = data[i];
  51. if (item.children) {
  52. for (let j = 0; j < item.children.length; j++) {
  53. const childItem = item.children[j];
  54. if (childItem.key === key) {
  55. item.children.push(newNode);
  56. break;
  57. } else if (childItem.children) {
  58. insertBro([childItem], key, newNode);
  59. }
  60. }
  61. }
  62. }
  63. return data;
  64. };
  65. setGData(insertBro(data, key, newNode));
  66. };
  67. /**
  68. * 删除当前节点
  69. * @param data 源数据
  70. * @param key 待删除节点key
  71. */
  72. const deleteNodeByKey = function (key: string | number | undefined) {
  73. const data = JSON.parse(JSON.stringify(gData));
  74. const delNode = (data: any[], key: string | number | undefined) => {
  75. for (let i = 0; i < data.length; i++) {
  76. const obj = data[i];
  77. if (obj.key === key) {
  78. data.splice(i, 1);
  79. break;
  80. } else if (obj.children) {
  81. delNode(obj.children, key);
  82. if (obj.children.length === 0) {
  83. delete obj.children;
  84. }
  85. }
  86. }
  87. };
  88. delNode(data, key);
  89. setGData(data);
  90. };
  91. /**
  92. * 更新子节点配置
  93. * @param oldData 旧数据
  94. * @param key 待更新子节点key
  95. * @param newData 更新后新数据
  96. */
  97. const updateTreeDataByKey = function (
  98. key: string | number | undefined,
  99. newData: any
  100. ) {
  101. const data = JSON.parse(JSON.stringify(gData));
  102. const updateNode = (
  103. oldData: any[],
  104. key: string | number | undefined,
  105. newData: any[]
  106. ) => {
  107. for (let i = 0; i < oldData.length; i++) {
  108. if (oldData[i].key === key) {
  109. oldData[i] = { ...oldData[i], ...newData };
  110. break;
  111. } else {
  112. if (Array.isArray(oldData[i].children)) {
  113. updateNode(oldData[i].children, key, newData);
  114. }
  115. }
  116. }
  117. };
  118. updateNode(data, key, newData);
  119. setGData(data);
  120. };
  121. /**
  122. * 上移/下移
  123. * @param data 源数据
  124. * @param key 目标key
  125. * @param direction 移动类型
  126. */
  127. const moveNodeInTreeByKey = function (
  128. key: string | number | undefined,
  129. direction: "UP" | "DOWN"
  130. ) {
  131. const data = JSON.parse(JSON.stringify(gData));
  132. const moveNode = (
  133. data: any[],
  134. key: string | number | undefined,
  135. direction: string
  136. ) => {
  137. const newData = [...data];
  138. for (let i = 0; i < newData.length; i++) {
  139. const item = newData[i];
  140. const itemLen = item.children.length;
  141. if (item.children) {
  142. for (let j = 0; j < itemLen; j++) {
  143. const childItem = item.children[j];
  144. if (childItem.key === key) {
  145. if (j === 0 && direction === "UP")
  146. message.info("已经处于第一位,无法上移");
  147. if (j === itemLen - 1 && direction === "DOWN")
  148. message.info("已经处于最后一位,无法下移");
  149. // splice (开始位置,移除元素个数,新增元素对象)
  150. if (direction === "UP") {
  151. item.children.splice(j, 1);
  152. item.children.splice(j - 1, 0, childItem);
  153. } else {
  154. item.children.splice(j, 1);
  155. item.children.splice(j + 1, 0, childItem);
  156. }
  157. break;
  158. } else if (childItem.children) {
  159. moveNode([childItem], key, direction);
  160. }
  161. }
  162. }
  163. }
  164. return newData;
  165. };
  166. setGData(moveNode(data, key, direction));
  167. };
  168. return {
  169. gData,
  170. insertNodeByKey,
  171. insertNodeInParentByKey,
  172. deleteNodeByKey,
  173. updateTreeDataByKey,
  174. moveNodeInTreeByKey,
  175. };
  176. };

写在最后

演示地址

完整代码

原文链接:https://www.cnblogs.com/plumliil/p/17546554.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号