大家好,我是小丞同学,一名大二的前端爱好者
这个系列文章是实战 jira 任务管理系统的一个学习总结
非常感谢你的阅读,不对的地方欢迎指正
愿你忠于自己,热爱生活
在上一篇文章中,我们实现了路由的跳转,实现了对应项目跳转到显示对应内容的看板页面,在这当中,我们编写了 useDocumentTitle 、useDebounce 这两个给 custom hook 。接下来我们将来处理看板部分的展示
知识点抢先看
封装 KanbanColumn 来布局页面
编写大量的 custom hook 来处理看板数据
对 useQuery 有进一步的了解
利用 filter 实现数据的统一性
一、处理看板数据的 custom hook
在这里我们需要先解决以下获取看板数据的问题,有了数据我们才能更好的驱动视图
我们将这些 hook 单独写在一个 kanban.ts 写在 util 文件夹内,这个文件夹中的 hook 都是一些复用性高的,和页面关系不大的 hook
1. useKanbans
这里获取数据的方法和前面获取项目数据的方法一样,我们采用 useQuery 来进行缓存看板数据,这里我们需要接收一个 param 作为参数,传递当前的 projectId 即可,当这个 id 变化时,表示切换了其他项目的看板,我们需要重新请求以下
export const useKanbans = (param?: Partial<Kanban>) => { // 采用 useHttp 来封装请求 const client = useHttp() // 映射一个 名为 kanbans 的缓存数据,当 param 变化时,重新发送请求,写入缓存 return useQuery<Kanban[]>(['kanbans', param], () => client('kanbans', { data: param })) }
封装好了 usekanbans ,我们已经能够获取项目中的看板数据了,接下来我们在封装一个 custom hook 来获取 projectId ,以实现 useKanBans 的用处
2. useProjectIdInUrl
我们在 kanban 文件夹,下的 util 中编写这段代码,因为它和项目有着直接的关系
首先在我们之前的路由处理中,我们将我们的 projectId 映射到了 url 上,我们可以通过解析这个 url 地址来得到当前页面请求的项目 id
这里我们采用 react-router 中的 hook 来得到 pathname,它的格式是这样的 /projects/1nban
因此我们通过正则表达式来获取出当中的数字也就是我们的 proejctId ,最后返回这个 id 的数字类型即可
export const useProjectIdInUrl = () => { const { pathname } = useLocation() // 返回的是一个数组 const id = pathname.match(/projects\/(\d+)/)?.[1] return Number(id) }
3. useProjectInUrl
有了我们的 projectId ,我们就可以使用通过它来获取我们的项目数据,这样我们就能获取到我们的项目的名称,显示到页面上
// 通过 id 获取项目信息 export const useProjectInUrl = () => useProject(useProjectIdInUrl())
使用
const { data: currentProject } = useProjectInUrl() <h1>{currentProject?.name}看板</h1>
写到这里我们已经能够获取到看板数据以及项目信息了,接下来我们需要来获取对应的任务信息
4. useKanbanSearchParams
为了避免我们获取到的看板数据是全部项目中的看板数据,我们需要将 id 转为 key-value 传递给 useKanbans 来获取数据
export const useKanbanSearchParams = () => ({ projectId: useProjectIdInUrl() })
5. useTasks
接着我们需要来获取 task 数据,也就是我们这个项目的任务数据
和获取 kanban 数据一样,我们需要采用 useQuery 来处理
export const useTasks = (param?: Partial<Task>) => { const client = useHttp() // 搜索框请求在这里触发 return useQuery<Task[]>(['tasks', param], () => client('tasks', { data: param })) }
在这里就讲讲类型吧~
在这里我们接收一个可选的参数,Task ,Task 是我们封装在 types 中的一个共享接口
export interface Task { id: number; name: string; // 经办人 processorId: number; projectId: number; // 任务组 epicId: number; kanbanId: number; // bug or task typeId: number; note: string; }
这里定义的都是后端返回的数据类型
Partial 的作用是,让接口中的变量都变成可选的
这样我们就也实现了对看板中的 task 获取,接下来同样的我们需要实现获取对应看板中的 task
6. useTasksSearchParams
为了让我们获取到的任务数据来自于当前的看板我们也需要封装一个 searchParams 来获取相应项目下的看板信息
export const useTaskSearchParams = () => ({ projectId: useProjectIdInUrl() })
在之后,我们会对这个 hook 进行改造
二、封装 KanbanColumn 渲染页面
1. 看板和任务数据统一
明确我们这个组件的作用,我们需要用它来渲染每一列的看板
大概是这样一个布局,首先,因为我们需要将任务渲染到对应的看板列表下,因此首先我们需要解决数据的问题
我们在 KanbanColumn 中获取数据,在这里我们需要十分明确,这个我们的这个组件它只是渲染一列,我们通过遍历实现多列,这个很关键
我们在 column 中获取所有的 task 数据,通过 filter 方法,将它筛选出来,这样,最后得到的就是和 kanbanId 匹配的 task 数据
const { data: allTasks } = useTasks(useTasksSearchParams()) // 对数据进行分类,返回的是三段数据,都是数组 const tasks = allTasks?.filter(task => task.kanbanId === kanban.id)
在这里有一个很有意思的问题
我们个每一个 column 都绑定了一个 useTasks ,按理说它应该会发送多次的请求 ,我们来看看到底是不是这样
在这里我们可以发现它一共发送了 2次请求,但是我启动的这个看板中有三个 column
不妨我们再多添加几个 column ,我们再来看看
在这里始终都是只有2个请求,那这是为什么呢?
其实在我们在遍历添加 kanbanColumns 组件时,只会发起一个请求,即使,我们给每一个 column 都绑定了 useTask
这是因为,我们采用的 react-query 的功劳,在我们采用 useQuery 时,如果在 2s 之内有相同的 queryKey 发出请求的话,就会合并这些请求,只会发出一个
现在我们已经有了每个看板下的 Task 数据了,我们只需要遍历渲染即可,这里我们采用的还是 Antd 组件库
2. useTaskTypes 处理不同类型任务的 icon
在我们的任务中又分为 bug 和 task,我们都会有相应的图标展示
在这里我们在 utils 下封装一个 useTaskTypes 来获取 task 的类型
export const useTaskTypes = () => { const client = useHttp() // 获取所有的task type return useQuery<TaskType[]>(['taskTypes'], () => client('taskTypes')) }
在这里我们封装一个 TaskTypeIcon 小组件,来返回类型对应的 icon ,这里我们只需要接收一个 taskid 作为参数,用来判断这个任务是什么类型
// 通过type渲染图片 const TaskTypeIcon = ({ id }: { id: number }) => { const { data: taskTypes } = useTaskTypes() const name = taskTypes?.find(taskType => taskType.id === id)?.name; if (!name) { return null } return <img alt={'task-icon'} src={name === 'task' ? taskIcon : bugIcon} /> }
三、处理任务的搜索功能
1. useTasksSearchParams
在我们前面已经有用到这个 hook 了,现在,我们需要添加一些代码,来实现搜索框的逻辑,在之前我们通过这个来返回用户 id 的对象,这个功能也不能遗忘噢~
export const useTasksSearchParams = () => { // 搜索内容 const [param] = useUrlQueryParam([ 'name', 'typeId', 'processorId', 'tagId' ]) // 获取当前的项目id用来获取看板数据 const projectId = useProjectIdInUrl() // 返回的数组,并监听 param变化 return useMemo(() => ({ projectId, typeId: Number(param.typeId) || undefined, processId: Number(param.processorId) || undefined, tagId: Number(param.tagId) || undefined, name: param.name }), [projectId, param]) }
在这里我们封装的这个方法,用于返回最小的 task 列表数据,这里需要实现的搜索功能在前面的项目搜索框也实现过了,采用 useSetUrlSearchParam 来修改当前的 url 地址,来造成数据的变化,又由于,我们这个 hook 返回的数据中的依赖项发生改变,造成了显示内容的改变,从而达到搜索效果
2. 重置按钮
在这里勇个比较有意思的按钮,清楚筛选器,它实现的方法请求非常的简单,我们只需要将所有的数据重置为 undefined ,我们的 clean 函数,就会讲 query 修理为空,这样我们返回的数据就会是全部的数据
const reset = () => { setSearchParams({ typeId: undefined, processId: undefined, tagId: undefined, name: undefined }) }
四、看板的增删改查功能
这部分的内容和之前的项目列表相似度很高,我们这里就不详细讲了,稍微解释以下这些 hook 的作用
1. useAddKanban
接着我们需要处理看板增删的 hook ,在这里我们有必要采用乐观更新来实现,不然在服务器请求慢时,造成页面假死过长
和前面一样,我们采用 useMutation 来封装 http 请求,返回一个被处理过的 mutate 请求方式或者 mutateAsync 异步请求方式
在这里我们接收了一个 queryKey 作为参数,这里它是一个数组第一个元素是缓存中的数据名称,第二个元素是它的重新刷新的依赖
export const useAddKanban = (queryKey: QueryKey) => { const client = useHttp() // 处理 http 请求 return useMutation( (params: Partial<Kanban>) => client(`kanbans`, { method: "POST", data: params }), // 配置乐观更新 useAddConfig(queryKey) ) }
在 config 配置中,我们将在 old 元素中,通过数组解构的方式,将新数据添加到了缓存中,这样我们就实现了对数据的更改
export const useAddConfig = (queryKey: QueryKey) => useConfig(queryKey, (target, old) => old ? [...old, target] : [])
2. useDeleteKanban
删除看板的 hook ,在这里我们采用同样的方法,采用的 config 也是我们之前就封装过
的,对于所有的增删改都成立的 hook
// 删除看板 export const useDeleteKanban = (queryKey: QueryKey) => { const client = useHttp() return useMutation( ({ id }: { id: number }) => client(`kanbans/${id}`, { method: "DELETE", }), useDeleteConfig(queryKey) ) }
在这里接收的参数只有 id ,删除看板的 id
五、任务的增删改查功能
增删改查的功能都差不多,只是传递的参数不一样罢了,在这里,我们就拿一个编辑功能来讲
我们首先封装了一个控制 modal 开关的 hook useTasksModel
const [form] = useForm() const { editingTaskId, editingTask, close } = useTasksModel() // 解构一个 task 方法 const { mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey()) // 添加一个删除任务的方法 const { mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey()) // 点击取消时,调用close同时清空表单
在这里我们暴露出了很多关于任务增删改查的方法,只要调用即可,这里我们在 modal 中,绑定了 onOk 以及 onCancel 方法
这里有个值得注意的地方
我们这次采用的是 mutateAsync 异步执行,因此我们需要采用 await 进行等待执行结果
const onCancel = () => { close() form.resetFields() } const onOk = async () => { // 获取表单数据 await editTask({ ...editingTask, ...form.getFieldsValue() }) // 关闭表单 close() }
总结
在这篇文章中我们做完了看板页面的制作,我们能学到这些东西
熟悉了增删改查的操作
了解了 useQuery 的用法
对 modal 组件有了更多的了解
了解了 react-query 能够优化请求次数