这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
为了和代码原始注释区分,个人理解部分使用 ///开头,此处和 三斜线指令没有关系,只是为了做区分。
往期回顾
useDynamicList
给列表项关联一个唯一 key,适用于 antd 表单的动态列表,其他场景可能会有问题。
import { useCallback, useRef, useState } from 'react';
export default <T>(initialValue: T[]) => {
const counterRef = useRef(-1);/// 存储最后一个key,自增
// key 存储器
/// 和 list 里面的内容一一对应
const keyList = useRef<number[]>([]);
// 内部方法
const setKey = useCallback((index: number) => {
counterRef.current += 1;
keyList.current.splice(index, 0, counterRef.current);
}, []);
const [list, setList] = useState(() => {
(initialValue || []).forEach((_, index) => {
setKey(index);
});
return initialValue || [];
});
/// 将列表设置为一个新的列表 newList ,不是将列表重置为最初的状态
const resetList = useCallback((newList: T[] = []) => {
keyList.current = []; /// key 存储器清空了,但是 key 没有重新从 0 开始自增
setList(() => {
(newList || []).forEach((_, index) => {
setKey(index);
});
return newList || [];
});
}, []);
/// 插入项,并在 keylist 中同样位置插入一个 key
const insert = useCallback((index: number, obj: T) => {
setList((l) => {
const temp = [...l];
temp.splice(index, 0, obj);
setKey(index);
return temp;
});
}, []);
/// 返回指定索引位置数据的 key
const getKey = useCallback((index: number) => keyList.current[index], []);
const getIndex = useCallback(
(key: number) => keyList.current.findIndex((ele) => ele === key),
[],
); /// 传入 key,反向查找对应的数据的索引值
/// 先给被合并数据设置key,然后合并
const merge = useCallback((index: number, obj: T[]) => {
setList((l) => {
const temp = [...l];
obj.forEach((_, i) => {
setKey(index + i);
});
temp.splice(index, 0, ...obj);
return temp;
});
}, []);
/// 替换数据,key不变,如果使用了 key 作为 react 的 key,则替换后该项不会触发重新渲染!!!
const replace = useCallback((index: number, obj: T) => {
setList((l) => {
const temp = [...l];
temp[index] = obj;
return temp;
});
}, []);
const remove = useCallback((index: number) => {
setList((l) => {
const temp = [...l];
temp.splice(index, 1);
/// try catch 用来了防止 current 不存在时程序崩溃
// remove keys if necessary
try {
keyList.current.splice(index, 1);
} catch (e) {
console.error(e);
}
return temp;
});
}, []);
const move = useCallback((oldIndex: number, newIndex: number) => {
if (oldIndex === newIndex) {
return;
}
setList((l) => {
const newList = [...l];
/// 先把旧索引处数据删除,然后移到新索引处
/// 等价于 newList.splice(newIndex, 0, ...newList.splice(oldIndex, 1))
const temp = newList.filter((_: {}, index: number) => index !== oldIndex);
temp.splice(newIndex, 0, newList[oldIndex]);
// move keys if necessary
try {
const keyTemp = keyList.current.filter((_: {}, index: number) => index !== oldIndex);
keyTemp.splice(newIndex, 0, keyList.current[oldIndex]);
keyList.current = keyTemp;
} catch (e) {
console.error(e);
}
return temp;
});
}, []);
const push = useCallback((obj: T) => {
setList((l) => {
setKey(l.length);
return l.concat([obj]); /// 使用 concat 是因为 concat 会返回合并后的新数组, push返回的是新数组的长度。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
});
}, []);
const pop = useCallback(() => {
// remove keys if necessary
try {
keyList.current = keyList.current.slice(0, keyList.current.length - 1);
} catch (e) {
console.error(e);
}
/// 同样,使用 slice 是因为返回的是操作后的新数组,而 pop 返回的是被删除的元素
setList((l) => l.slice(0, l.length - 1));
}, []);
const unshift = useCallback((obj: T) => {
setList((l) => {
setKey(0);
return [obj].concat(l);
});
}, []);
/// 这不是一个能在所有场景下使用的方法,是针对 demo 中的 form.getFieldsValue() 获取表单数据然后排序的场景,不了解这点看下面的代码可能会比较懵。
/// 因为需要 sortForm 方法生效,需要满足下面几点要求:
/// 1、被传入的 result 源数据必须始终保持最初的索引,因为只有这样他们的 index 才刚好和内部存储的 key 相同
/// 2、如果使用了 resetList 重置,新增加的 result 索引需要从重置前的最大值开始,而不能从 0 开始。因为重置时没有将 key 重设为 0 开始。例如:本来有三条数据,索引为:0,1,5,resetLis之后新增一项得到的 result 索引应该为 6
const sortForm = useCallback(
(result: unknown[]) =>
result
.map((item, index) => ({ key: index, item })) // add index into obj
.sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table
.filter((item) => !!item.item) // remove undefined(s)
.map((item) => item.item), // retrive the data
[],
);
const shift = useCallback(() => {
// remove keys if necessary
try {
keyList.current = keyList.current.slice(1, keyList.current.length);
} catch (e) {
console.error(e);
}
setList((l) => l.slice(1, l.length));
}, []);
return {
list,
insert,
merge,
replace,
remove,
getKey,
getIndex,
move,
push,
pop,
unshift,
shift,
sortForm,
resetList,
};
};
参考资料
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。