hook 即為勾點,是一種特殊的函數,它可以讓你在函數式元件中使用一些 react 特性,目前在 react 中常用的 hook 有以下幾類
以上各種 hook 的用法在筆記檔案中均有記錄,如有興趣可以前往閱覽.
自定義 Hook 是指在 React 中編寫的自定義函數,以便在各個元件之間重用邏輯。通過自定義 Hook,我們可以將一些邏輯抽象出來,使它們可以在不同的元件中共用和複用。
自定義 Hook 的命名以 「use」 開頭,這是為了遵循 React 的 Hook 命名規範。自定義 Hook 可以使用任何 React 的內建 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 函數中我們要實現以下功能
/**
* 插入子級
* @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
函數程式碼中傳入了兩個引數key
和 newNode
,這兩個分別代表當前操作節點物件的 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));
};
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,
};
};