什么是 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. 19.13备库duplicate恢复新主库(二)

    问题描述:主备两个库不在同一个机房,此时想从这一套库中在复制一套可读可写的新库出来.网络带宽要求比较高,需要从备库中使用备份在起一个新库,也要测试下使用duplicate从备库能够在复制一个新库.经过 ...

  2. 虚拟机安装linux操作系统中IP设置

    问题描述:用虚拟机安装linux操作系统时,不选择分配IP,系统默认会分配动态IP,如果是临时搭建,就可以默认动态IP,也可以使用连接工具连接虚拟机.但如果是长期使用,选择使用固定的IP会更好,需要手 ...

  3. docker 容器操作、应用部署、mysql,redis,nginx、迁移与备份、Dockerfile

    容器操作 # 启动容器 docker start 容器id # 停止容器 docker stop 容器id # 文件拷贝 先创建文件 mkdir:文件夹 vi vim touch:文件 # 容器的文件 ...

  4. Proxy 与 Object.defineProperty 优劣对比?

    Proxy的优势如下 1.Proxy 可以直接监听对象而不是属性(Object.defineProperty一次只能监视一个属性,如果要监视一个对象,那么需要遍历这个对象),可以直接监听数组的变化(O ...

  5. css盒子水平垂直居中的几种方式

    第一种:son盒子中定位的上下左右全部为0,然后margin:auto 1 <!DOCTYPE html> 2 <html lang="en"> 3 < ...

  6. Pandas的使用

    在数据分析工作中,Pandas 的使用频率是很高的,一方面是因为 Pandas 提供的基础数据结构 DataFrame 与 json 的契合度很高,转换起来很方便.另一方面,如果日常的数据清理工作不是 ...

  7. Vue3 element-plus 下拉分页 select分页

    由于用 input 实现下拉分页不太理想,转换了一个角度,用 select 实现,以下是具体实现(script-setup TS) script-setup <script lang=" ...

  8. ThreadLocal实现原理和使用场景

    ThreadLocal是线程本地变量,每个线程中都存在副本. 实现原理: 每个线程中都有一个ThreadLocalMap,而ThreadLocalMap中的key即是ThreadLocal.  内存泄 ...

  9. 关于linux下Qt5.7.0安装中文输入法无法显示的问题

    关于linux下Qt5.7.0安装中文输入法无法显示的问题 本文是以我自己系统ubuntu-x64 + fcitx + Qt5.7.0为例: sudo apt-get install fcitx-fr ...

  10. ai问答:使用 Vue3 组合式API 和 TS 父子组件共享数据

    这是一个使用 Vue3 组合式 API 和 TypeScript 的简单父子组件共享数据示例 父组件 Parent.vue: <template> <div> <p> ...