Skip to content

Commit

Permalink
feat(packages): hooks: add use-array & example
Browse files Browse the repository at this point in the history
  • Loading branch information
mufeng889 committed Oct 6, 2024
1 parent f92972e commit 02483b5
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 22 deletions.
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ import useHookTable from './use-table';
import useRequest from './use-request';
export { useBoolean, useLoading, useCountDownTimer, useSvgIconRender, useHookTable, useRequest };

export { default as useArray } from './use-array';

export * from './use-table';
124 changes: 124 additions & 0 deletions packages/hooks/src/use-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { useState } from 'react';

type ArrayState<T> = T[];
type ArrayActions<T, K extends keyof T> = {
updateState: (newState: T[]) => void;
push: (...newItems: T[]) => void;
unshift: (...newItems: T[]) => void;
remove: (itemKey: T[K]) => void;
up: (itemKey: T[K]) => void;
down: (itemKey: T[K]) => void;
pop: () => void;
shift: () => void;
reverse: () => void;
sort: (compareFn?: (a: T, b: T) => number) => void;
splice: (start: number, deleteCount?: number, ...items: T[]) => void;
};

export default function useArray<T, K extends keyof T>(initState: T[], key?: K): [ArrayState<T>, ArrayActions<T, K>] {
const [state, setState] = useState(initState);

const resolvedKey = (key ?? 'id') as K;

const updateState = (newState: T[]) => {
setState(newState);
};

const push = (...newItems: T[]) => {
setState(prevState => {
// 确保新添加的元素的 key 是唯一的
const newState = [...prevState, ...newItems];
return newState.filter(
(item, index, self) => index === self.findIndex(t => t[resolvedKey] === item[resolvedKey])
);
});
};

const unshift = (...newItems: T[]) => {
setState(prevState => {
// 确保新添加的元素的 key 是唯一的
const newState = [...newItems, ...prevState];
return newState.filter(
(item, index, self) => index === self.findIndex(t => t[resolvedKey] === item[resolvedKey])
);
});
};

const remove = (itemKey: T[K]) => {
setState(prevState => prevState.filter(i => i[resolvedKey] !== itemKey));
};

// 上移函数
const up = (itemKey: T[K]) => {
setState(prevState => {
const index = prevState.findIndex(i => i[resolvedKey] === itemKey);

// 如果该元素在第一个位置,就不能上移
if (index <= 0) return prevState;

// 交换当前元素与上一个元素的位置
const newState = [...prevState];

[newState[index], newState[index - 1]] = [newState[index - 1], newState[index]];

return newState;
});
};

// 下移函数
const down = (itemKey: T[K]) => {
setState(prevState => {
const index = prevState.findIndex(i => i[resolvedKey] === itemKey);

// 如果该元素已经在最后一个位置或找不到该元素,就不能下移
if (index === prevState.length - 1 || index === -1) return prevState;

// 交换当前元素与下一个元素的位置
const newState = [...prevState];

[newState[index], newState[index + 1]] = [newState[index + 1], newState[index]];

return newState;
});
};

const pop = () => {
setState(prevState => {
// 使用 slice(0, -1) 来移除最后一个元素
return prevState.slice(0, -1);
});
};

const shift = () => {
setState(prevState => {
// 使用 slice(1) 来移除第一个元素
return prevState.slice(1);
});
};

const reverse = () => {
setState(prevState => {
// 使用 spread 运算符 ... 和 reverse() 来反转数组
return [...prevState].reverse();
});
};

const sort = (compareFn?: (a: T, b: T) => number) => {
setState(prevState => {
// 使用 spread 运算符 ... 和 sort() 方法进行排序
return [...prevState].sort(compareFn);
});
};

const splice = (start: number, deleteCount?: number, ...items: T[]) => {
const end = deleteCount ?? 0;
setState(prevState => {
// 使用 spread 运算符 ... 和 splice() 方法进行修改
const newState = [...prevState];
newState.splice(start, end, ...items);
return newState;
});
};

return [state, { updateState, push, unshift, remove, up, down, pop, shift, sort, splice, reverse }];
}
138 changes: 116 additions & 22 deletions src/pages/home/modules/ProjectNews.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,135 @@
import { Card, List } from 'antd';
import { AnimatePresence, motion } from 'framer-motion';
import { useArray } from '@sa/hooks';
import SoybeanAvatar from '@/components/stateful/SoybeanAvatar';

interface NewsItem {
id: number;
content: string;
time: string;
}
const variants = {
hidden: { opacity: 0, y: -20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
exit: { opacity: 0, x: 200, transition: { duration: 0.3 } }
};

const ProjectNews = () => {
const { t } = useTranslation();
const newses: NewsItem[] = [

const [newses, { push, remove, unshift, up, down, pop, shift, reverse, sort }] = useArray([
{ id: 1, content: t('page.home.projectNews.desc1'), time: '2021-05-28 22:22:22' },
{ id: 2, content: t('page.home.projectNews.desc2'), time: '2021-10-27 10:24:54' },
{ id: 2, content: t('page.home.projectNews.desc2'), time: '2023-10-27 10:24:54' },
{ id: 3, content: t('page.home.projectNews.desc3'), time: '2021-10-31 22:43:12' },
{ id: 4, content: t('page.home.projectNews.desc4'), time: '2021-11-03 20:33:31' },
{ id: 4, content: t('page.home.projectNews.desc4'), time: '2022-11-03 20:33:31' },
{ id: 5, content: t('page.home.projectNews.desc5'), time: '2021-11-07 22:45:32' }
];
]);

const sortByTimeDesc = () => {
sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
};

return (
<Card
extra={<a className="text-primary">{t('page.home.projectNews.moreNews')}</a>}
extra={[
<AButton
onClick={reverse}
type="text"
key="reverse"
>
反转
</AButton>,
<AButton
onClick={sortByTimeDesc}
type="text"
key="reverse"
>
以时间排序
</AButton>,
<AButton
onClick={() => unshift({ id: 1, content: '我是第一个', time: '2021-11-07 22:45:32' })}
type="text"
key="unshift"
>
从头添加
</AButton>,
<AButton
onClick={shift}
type="text"
key="shift"
>
删除头部
</AButton>,
<AButton
onClick={() => push({ id: 6, content: '我是第六个', time: '2021-11-07 22:45:32' })}
type="text"
key="PUSH"
>
尾部添加
</AButton>,
<AButton
onClick={pop}
type="text"
key="pop"
>
删除尾部
</AButton>,
<a
key="a"
className="ml-8px text-primary"
>
{t('page.home.projectNews.moreNews')}
</a>
]}
bordered={false}
size="small"
className="card-wrapper"
title={t('page.home.projectNews.title')}
>
<List
dataSource={newses}
renderItem={item => (
<List.Item key={item.time}>
<List.Item.Meta
avatar={<SoybeanAvatar className="size-48px!" />}
title={item.content}
description={item.time}
></List.Item.Meta>
</List.Item>
)}
></List>
<AnimatePresence mode="popLayout">
<List
dataSource={newses}
renderItem={item => (
<motion.div
key={item.id}
variants={variants} // 应用定义的动画 variants
initial="hidden" // 初始状态
animate="visible" // 动画目标状态
exit="exit" // 退出时动画
layout // 处理上移、下移等排序动画
>
<List.Item
actions={[
<AButton
onClick={() => up(item.id)}
size="small"
key="up"
>
上移
</AButton>,

<AButton
onClick={() => remove(item.id)}
danger
size="small"
key="del"
>
删除
</AButton>,
<AButton
onClick={() => down(item.id)}
size="small"
key="down"
>
下移
</AButton>
]}
>
<List.Item.Meta
avatar={<SoybeanAvatar className="size-48px!" />}
title={item.content}
description={item.time}
></List.Item.Meta>
</List.Item>
</motion.div>
)}
></List>
</AnimatePresence>
</Card>
);
};
Expand Down

0 comments on commit 02483b5

Please sign in to comment.