大家好,我是小丞同学,一名大二的前端爱好者
这个系列文章是实战 jira 任务管理系统的一个学习总结
非常感谢你的阅读,不对的地方欢迎指正
愿你忠于自己,热爱生活
在上一篇文章中,我们实现了路由的跳转,实现了对应项目跳转到显示对应内容的看板页面,在这当中,我们编写了 useDocumentTitle
、useDebounce
这两个给 custom hook
。接下来我们将来处理看板部分的展示
KanbanColumn
来布局页面custom hook
来处理看板数据useQuery
有进一步的了解filter
实现数据的统一性在这里我们需要先解决以下获取看板数据的问题,有了数据我们才能更好的驱动视图
我们将这些 hook
单独写在一个 kanban.ts
写在 util
文件夹内,这个文件夹中的 hook
都是一些复用性高的,和页面关系不大的 hook
这里获取数据的方法和前面获取项目数据的方法一样,我们采用 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
的用处
我们在 kanban
文件夹,下的 util
中编写这段代码,因为它和项目有着直接的关系
首先在我们之前的路由处理中,我们将我们的 projectId
映射到了 url
上,我们可以通过解析这个 url
地址来得到当前页面请求的项目 id
这里我们采用 react-router
中的 hook
来得到 pathname
,它的格式是这样的 /projects/1/kanban
因此我们通过正则表达式来获取出当中的数字也就是我们的 proejctId
,最后返回这个 id
的数字类型即可
export const useProjectIdInUrl = () => {
const {
pathname } = useLocation()
// 返回的是一个数组
const id = pathname.match(/projects\/(\d+)/)?.[1]
return Number(id)
}
有了我们的 projectId
,我们就可以使用通过它来获取我们的项目数据,这样我们就能获取到我们的项目的名称,显示到页面上
// 通过 id 获取项目信息
export const useProjectInUrl = () => useProject(useProjectIdInUrl())
使用
const {
data: currentProject } = useProjectInUrl()
<h1>{
currentProject?.name}看板</h1>
写到这里我们已经能够获取到看板数据以及项目信息了,接下来我们需要来获取对应的任务信息
为了避免我们获取到的看板数据是全部项目中的看板数据,我们需要将 id
转为 key-value
传递给 useKanbans
来获取数据
export const useKanbanSearchParams = () => ({
projectId: useProjectIdInUrl() })
接着我们需要来获取 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
为了让我们获取到的任务数据来自于当前的看板我们也需要封装一个 searchParams
来获取相应项目下的看板信息
export const useTaskSearchParams = () => ({
projectId: useProjectIdInUrl() })
在之后,我们会对这个 hook
进行改造
明确我们这个组件的作用,我们需要用它来渲染每一列的看板
大概是这样一个布局,首先,因为我们需要将任务渲染到对应的看板列表下,因此首先我们需要解决数据的问题
我们在 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
组件库
在我们的任务中又分为 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} />
}
在我们前面已经有用到这个 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
返回的数据中的依赖项发生改变,造成了显示内容的改变,从而达到搜索效果
在这里勇个比较有意思的按钮,清楚筛选器,它实现的方法请求非常的简单,我们只需要将所有的数据重置为 undefined
,我们的 clean
函数,就会讲 query
修理为空,这样我们返回的数据就会是全部的数据
const reset = () => {
setSearchParams({
typeId: undefined,
processId: undefined,
tagId: undefined,
name: undefined
})
}
这部分的内容和之前的项目列表相似度很高,我们这里就不详细讲了,稍微解释以下这些 hook
的作用
接着我们需要处理看板增删的 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] : [])
删除看板的 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
能够优化请求次数最后,可能在很多地方讲诉的不够清晰,请见谅
如果文章有什么错误的地方,或者有什么疑问,欢迎留言,也欢迎私信交流