Skip to content

Commit 02483b5

Browse files
committed
feat(packages): hooks: add use-array & example
1 parent f92972e commit 02483b5

File tree

3 files changed

+242
-22
lines changed

3 files changed

+242
-22
lines changed

packages/hooks/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ import useHookTable from './use-table';
66
import useRequest from './use-request';
77
export { useBoolean, useLoading, useCountDownTimer, useSvgIconRender, useHookTable, useRequest };
88

9+
export { default as useArray } from './use-array';
10+
911
export * from './use-table';

packages/hooks/src/use-array.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { useState } from 'react';
2+
3+
type ArrayState<T> = T[];
4+
type ArrayActions<T, K extends keyof T> = {
5+
updateState: (newState: T[]) => void;
6+
push: (...newItems: T[]) => void;
7+
unshift: (...newItems: T[]) => void;
8+
remove: (itemKey: T[K]) => void;
9+
up: (itemKey: T[K]) => void;
10+
down: (itemKey: T[K]) => void;
11+
pop: () => void;
12+
shift: () => void;
13+
reverse: () => void;
14+
sort: (compareFn?: (a: T, b: T) => number) => void;
15+
splice: (start: number, deleteCount?: number, ...items: T[]) => void;
16+
};
17+
18+
export default function useArray<T, K extends keyof T>(initState: T[], key?: K): [ArrayState<T>, ArrayActions<T, K>] {
19+
const [state, setState] = useState(initState);
20+
21+
const resolvedKey = (key ?? 'id') as K;
22+
23+
const updateState = (newState: T[]) => {
24+
setState(newState);
25+
};
26+
27+
const push = (...newItems: T[]) => {
28+
setState(prevState => {
29+
// 确保新添加的元素的 key 是唯一的
30+
const newState = [...prevState, ...newItems];
31+
return newState.filter(
32+
(item, index, self) => index === self.findIndex(t => t[resolvedKey] === item[resolvedKey])
33+
);
34+
});
35+
};
36+
37+
const unshift = (...newItems: T[]) => {
38+
setState(prevState => {
39+
// 确保新添加的元素的 key 是唯一的
40+
const newState = [...newItems, ...prevState];
41+
return newState.filter(
42+
(item, index, self) => index === self.findIndex(t => t[resolvedKey] === item[resolvedKey])
43+
);
44+
});
45+
};
46+
47+
const remove = (itemKey: T[K]) => {
48+
setState(prevState => prevState.filter(i => i[resolvedKey] !== itemKey));
49+
};
50+
51+
// 上移函数
52+
const up = (itemKey: T[K]) => {
53+
setState(prevState => {
54+
const index = prevState.findIndex(i => i[resolvedKey] === itemKey);
55+
56+
// 如果该元素在第一个位置,就不能上移
57+
if (index <= 0) return prevState;
58+
59+
// 交换当前元素与上一个元素的位置
60+
const newState = [...prevState];
61+
62+
[newState[index], newState[index - 1]] = [newState[index - 1], newState[index]];
63+
64+
return newState;
65+
});
66+
};
67+
68+
// 下移函数
69+
const down = (itemKey: T[K]) => {
70+
setState(prevState => {
71+
const index = prevState.findIndex(i => i[resolvedKey] === itemKey);
72+
73+
// 如果该元素已经在最后一个位置或找不到该元素,就不能下移
74+
if (index === prevState.length - 1 || index === -1) return prevState;
75+
76+
// 交换当前元素与下一个元素的位置
77+
const newState = [...prevState];
78+
79+
[newState[index], newState[index + 1]] = [newState[index + 1], newState[index]];
80+
81+
return newState;
82+
});
83+
};
84+
85+
const pop = () => {
86+
setState(prevState => {
87+
// 使用 slice(0, -1) 来移除最后一个元素
88+
return prevState.slice(0, -1);
89+
});
90+
};
91+
92+
const shift = () => {
93+
setState(prevState => {
94+
// 使用 slice(1) 来移除第一个元素
95+
return prevState.slice(1);
96+
});
97+
};
98+
99+
const reverse = () => {
100+
setState(prevState => {
101+
// 使用 spread 运算符 ... 和 reverse() 来反转数组
102+
return [...prevState].reverse();
103+
});
104+
};
105+
106+
const sort = (compareFn?: (a: T, b: T) => number) => {
107+
setState(prevState => {
108+
// 使用 spread 运算符 ... 和 sort() 方法进行排序
109+
return [...prevState].sort(compareFn);
110+
});
111+
};
112+
113+
const splice = (start: number, deleteCount?: number, ...items: T[]) => {
114+
const end = deleteCount ?? 0;
115+
setState(prevState => {
116+
// 使用 spread 运算符 ... 和 splice() 方法进行修改
117+
const newState = [...prevState];
118+
newState.splice(start, end, ...items);
119+
return newState;
120+
});
121+
};
122+
123+
return [state, { updateState, push, unshift, remove, up, down, pop, shift, sort, splice, reverse }];
124+
}

src/pages/home/modules/ProjectNews.tsx

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,135 @@
11
import { Card, List } from 'antd';
2+
import { AnimatePresence, motion } from 'framer-motion';
3+
import { useArray } from '@sa/hooks';
24
import SoybeanAvatar from '@/components/stateful/SoybeanAvatar';
35

4-
interface NewsItem {
5-
id: number;
6-
content: string;
7-
time: string;
8-
}
6+
const variants = {
7+
hidden: { opacity: 0, y: -20 },
8+
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
9+
exit: { opacity: 0, x: 200, transition: { duration: 0.3 } }
10+
};
911

1012
const ProjectNews = () => {
1113
const { t } = useTranslation();
12-
const newses: NewsItem[] = [
14+
15+
const [newses, { push, remove, unshift, up, down, pop, shift, reverse, sort }] = useArray([
1316
{ id: 1, content: t('page.home.projectNews.desc1'), time: '2021-05-28 22:22:22' },
14-
{ id: 2, content: t('page.home.projectNews.desc2'), time: '2021-10-27 10:24:54' },
17+
{ id: 2, content: t('page.home.projectNews.desc2'), time: '2023-10-27 10:24:54' },
1518
{ id: 3, content: t('page.home.projectNews.desc3'), time: '2021-10-31 22:43:12' },
16-
{ id: 4, content: t('page.home.projectNews.desc4'), time: '2021-11-03 20:33:31' },
19+
{ id: 4, content: t('page.home.projectNews.desc4'), time: '2022-11-03 20:33:31' },
1720
{ id: 5, content: t('page.home.projectNews.desc5'), time: '2021-11-07 22:45:32' }
18-
];
21+
]);
22+
23+
const sortByTimeDesc = () => {
24+
sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
25+
};
26+
1927
return (
2028
<Card
21-
extra={<a className="text-primary">{t('page.home.projectNews.moreNews')}</a>}
29+
extra={[
30+
<AButton
31+
onClick={reverse}
32+
type="text"
33+
key="reverse"
34+
>
35+
反转
36+
</AButton>,
37+
<AButton
38+
onClick={sortByTimeDesc}
39+
type="text"
40+
key="reverse"
41+
>
42+
以时间排序
43+
</AButton>,
44+
<AButton
45+
onClick={() => unshift({ id: 1, content: '我是第一个', time: '2021-11-07 22:45:32' })}
46+
type="text"
47+
key="unshift"
48+
>
49+
从头添加
50+
</AButton>,
51+
<AButton
52+
onClick={shift}
53+
type="text"
54+
key="shift"
55+
>
56+
删除头部
57+
</AButton>,
58+
<AButton
59+
onClick={() => push({ id: 6, content: '我是第六个', time: '2021-11-07 22:45:32' })}
60+
type="text"
61+
key="PUSH"
62+
>
63+
尾部添加
64+
</AButton>,
65+
<AButton
66+
onClick={pop}
67+
type="text"
68+
key="pop"
69+
>
70+
删除尾部
71+
</AButton>,
72+
<a
73+
key="a"
74+
className="ml-8px text-primary"
75+
>
76+
{t('page.home.projectNews.moreNews')}
77+
</a>
78+
]}
2279
bordered={false}
2380
size="small"
2481
className="card-wrapper"
2582
title={t('page.home.projectNews.title')}
2683
>
27-
<List
28-
dataSource={newses}
29-
renderItem={item => (
30-
<List.Item key={item.time}>
31-
<List.Item.Meta
32-
avatar={<SoybeanAvatar className="size-48px!" />}
33-
title={item.content}
34-
description={item.time}
35-
></List.Item.Meta>
36-
</List.Item>
37-
)}
38-
></List>
84+
<AnimatePresence mode="popLayout">
85+
<List
86+
dataSource={newses}
87+
renderItem={item => (
88+
<motion.div
89+
key={item.id}
90+
variants={variants} // 应用定义的动画 variants
91+
initial="hidden" // 初始状态
92+
animate="visible" // 动画目标状态
93+
exit="exit" // 退出时动画
94+
layout // 处理上移、下移等排序动画
95+
>
96+
<List.Item
97+
actions={[
98+
<AButton
99+
onClick={() => up(item.id)}
100+
size="small"
101+
key="up"
102+
>
103+
上移
104+
</AButton>,
105+
106+
<AButton
107+
onClick={() => remove(item.id)}
108+
danger
109+
size="small"
110+
key="del"
111+
>
112+
删除
113+
</AButton>,
114+
<AButton
115+
onClick={() => down(item.id)}
116+
size="small"
117+
key="down"
118+
>
119+
下移
120+
</AButton>
121+
]}
122+
>
123+
<List.Item.Meta
124+
avatar={<SoybeanAvatar className="size-48px!" />}
125+
title={item.content}
126+
description={item.time}
127+
></List.Item.Meta>
128+
</List.Item>
129+
</motion.div>
130+
)}
131+
></List>
132+
</AnimatePresence>
39133
</Card>
40134
);
41135
};

0 commit comments

Comments
 (0)