什么是 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. Spring中Bean的实例化详细流程

    还是举个例子,我有一个朋友小汪他远赴南方某城市打工.然后安定下来后他的朋友很想来家里玩,但是呢我这个朋友家里搞的很乱,所以他不好意思请朋友来家里玩.这时我的另一个朋友说那请一个保姆把家里好好整理一下就 ...

  2. SQL concat_ws, collect_set, 和explode合并使用

    1. 背景 有一个这样的数据集:字段和字段的值是两列 目的是将这个数据转换成规整的一个特征是一列的数据: 2. 做法 第一步:先造出列 select ucid ,CASE WHEN type ='性别 ...

  3. 基于Containerd容器引擎和kubeadm工具部署K8sv1.26.3

    前文我了解了基于ubuntu2204部署containerd容器引擎以及containerd客户端工具的部署和使用相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1 ...

  4. 【python基础】定时任务框架APScheduler

    1. 基础概念 APScheduler是python中较为简洁直观的定时任务框架,提供了基于固定日期.时间间隔.crontab表达式三种任务类型,并且可以持久化任务(如:将定时任务保存到mysql中) ...

  5. Springboot整合Flowable6.x导出bpmn20

    项目源码仓库 BPMN2.0(Business Process Model and Notation)是一套业务流程模型与符号建模标准,以XML为载体,以符号可视化业务,支持精准的执行语义来描述元素的 ...

  6. Kubernetes中使用Helm2的安全风险

    参考 http://rui0.cn/archives/1573 英文文章  https://blog.ropnop.com/attacking-default-installs-of-helm-on- ...

  7. Django笔记二十九之中间件介绍

    本文首发于公众号:Hunter后端 原文链接:Django笔记二十九之中间件介绍 这一节介绍一下 Django 的中间件. 关于中间件,官方文档的解释为:中间件是一个嵌入 Django 系统的 req ...

  8. Sitecore10 Demo演示环境Azure一键部署(Step By Step Guide to installing Sitecore10 in Azure Paas)

    本文演示Sitecore XP Single(XP0)在Azure上的一键部署,即"30分钟生成Sitecore演示环境"的一环. 关于XP(即Sitecore Experienc ...

  9. C#使用词嵌入向量与向量数据库为大语言模型(LLM)赋能长期记忆实现私域问答机器人落地

    本文将探讨如何使用c#开发基于大语言模型的私域聊天机器人落地.大语言模型(Large Language Model,LLM 这里主要以chatgpt为代表的的文本生成式人工智能)是一种利用深度学习方法 ...

  10. Linux xfs文件系统stat命令Birth字段为空的原因探究

    在Linux平台找出某个目录下创建时间最早的文件,测试验证脚本结果是否准确的过程中发现一个很有意思的现象,stat命令在一些平台下Birth字段有值,而在一些平台则为空值,如下所示: RHEL 8.7 ...