什么是 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,
};
};

写在最后

演示地址

完整代码

React中编写操作树形数据的自定义Hook的更多相关文章

  1. js中如何操作json数据

    一.要想熟练的操作json数据,就先要了解json数据的结构,json有两种结构:对象和数组. 1.对象 一个对象以“{”开始,“}”结束.每个“名称”后跟一个“:”:“‘名称/值’ 对”之间使用“, ...

  2. java通过poi读取excel中的日期类型数据或自定义类型日期

    Java 读取Excel表格日期类型数据的时候,读出来的是这样的  12-十月-2019,而Excel中输入的是 2019/10/12 或 2019-10-12 poi处理excel时,当excel没 ...

  3. DotNetBar中Supergrid显示树形数据

    1.向窗体中拖一个Supergrid控件 2.添加列ID,NAME,MATH,CN,SEX 3.在任务窗格中勾选“Show Tree Lines”和“Show Tree Buttons” 4.添加数据 ...

  4. Web中树形数据(层级关系数据)的实现—以行政区树为例

    在Web开发中常常遇到树形数据的操作,如菜单.组织机构.行政区(省.市.县)等具有层级关系的数据. 以下以行政区为例说明树形数据(层级关系数据)的存储以及实现,效果如图所看到的. 1 数据库表结构设计 ...

  5. JAVA DOM4j解析XML数据到自定义javabean

    我们获取xml中的数据,一般以面向对象的思想去处理这些数据.因此,我们需要自定义类来封装解析出来的数据,以方便我们操作这些数据. 自定义的java类,称为javabean. 自定义Contact类代码 ...

  6. Postgres 优雅存储树形数据

    碰到一个树形数据需要存储再数据控制,碰到以下两个问题: 在PG数据库中如何表达树形数据 如何有效率的查询以任意节点为Root的子树 测试数据 为了更加简单一些,我们将使用一下数据 Section A ...

  7. 在react中使用redux并实现计数器案例

    React + Redux 在recat中不使用redux 时遇到的问题 在react中组件通信的数据是单向的,顶层组件可以通过props属性向下层组件传递数据,而下层组件不能向上层组件传递数据,要实 ...

  8. react中父组件给子组件传值

    子组件 state = { msg: 'a' } render(){ return<h1>{this.state.msg}</h1> } setInfo = (val)=> ...

  9. 大数据学习day25------spark08-----1. 读取数据库的形式创建DataFrame 2. Parquet格式的数据源 3. Orc格式的数据源 4.spark_sql整合hive 5.在IDEA中编写spark程序(用来操作hive) 6. SQL风格和DSL风格以及RDD的形式计算连续登陆三天的用户

    1. 读取数据库的形式创建DataFrame DataFrameFromJDBC object DataFrameFromJDBC { def main(args: Array[String]): U ...

  10. 在 WF 4 中编写自定义控制流活动

    在 WF 4 中编写自定义控制流活动 Leon Welicki 控制流是指组织和执行程序中各个指令的方法. 在 Windows Workflow Foundation 4 (WF 4) 中,控制流活动 ...

随机推荐

  1. 【Mybatis】(一)

    Mybatis简介 提供持久层框架包括SQL Maps和Data Access Objects(DAO). SQL Maps提供数据库数据和java数据的映射关系,换句话说即是封装JDBC的过程. D ...

  2. 实现声明式锁,支持分布式锁自定义锁、SpEL和结合事务

    目录 2.实现 2.1 定义注解 2.2 定义锁接口 2.3 锁的实现 2.3.1 什么是SPI 2.3.2 通过SPI实现锁的多个实现类 2.3.3 通过SPI自定义实现锁 3.定义切面 3.1 切 ...

  3. Qt开发技术:Q3D图表开发笔记(三):Q3DSurface三维曲面图介绍、Demo以及代码详解

    前言   qt提供了q3d进行三维开发,虽然这个框架没有得到大量运用也不是那么成功,性能上也有很大的欠缺,但是普通的点到为止的应用展示还是可以的.  其中就包括华丽绚烂的三维图表,数据量不大的时候是可 ...

  4. js与java对json的操作

    JSON呢,是现在大部分,并且主流的传递数据的方式. 今天讲一下javascript的java对json的操作 提到js,当下就有一个比较主流的插件,vue.js,这个插件程序员没用过也都听说过吧, ...

  5. [C++提高编程] 3.6 queue 容器

    文章目录 3.6 queue 容器 3.6.1 queue 基本概念 3.6.2 queue 常用接口 3.6 queue 容器 3.6.1 queue 基本概念 概念:Queue是一种先进先出(Fi ...

  6. git拉取代码总提示输入密码解决方法

    公司用的gitlab,在项目拉取(git clone)和更新(git pull)的时候,每次都提示输入用户名密码,不胜其烦,解决方法如下: # 首先 git config --global crede ...

  7. 2021-08-23:超级水王问题。扩展1:摩尔投票。扩展2:给定一个正数K,返回所有出现次数>N/K的数。

    2021-08-23:超级水王问题.扩展1:摩尔投票.扩展2:给定一个正数K,返回所有出现次数>N/K的数. 福大大 答案2021-08-23: 扩展1: 1.如果无候选,当前数就是候选,血为1 ...

  8. 2021-09-02:IP 到 CIDR。给定起始IP和整数n,返回长度最小的CIDR块。力扣751。比如:ip=255.0.0.7,n=10,输出:[“255.0.0.7/32“,“255.0.0.

    2021-09-02:IP 到 CIDR.给定起始IP和整数n,返回长度最小的CIDR块.力扣751.比如:ip=255.0.0.7,n=10,输出:["255.0.0.7/32" ...

  9. pupstudy的使用

    打开环境 点击管理--打开根目录 把靶场放在www文件夹里 网页打开127.0.0.1/靶场文件名即可

  10. 代码随想录算法训练营Day55 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day55 动态规划| 392.判断子序列 115.不同的子序 392.判断子序列 题目链接:392.判断子序列 给定字符串 s 和 t ,判断 s 是否为 ...