Hooks + TS 搭建一个任务管理系统(六)-- 看板页面展示

大家好,我是小丞同学,一名大二的前端爱好者

这个系列文章是实战 jira 任务管理系统的一个学习总结

非常感谢你的阅读,不对的地方欢迎指正

愿你忠于自己,热爱生活

在上一篇文章中,我们实现了路由的跳转,实现了对应项目跳转到显示对应内容的看板页面,在这当中,我们编写了 useDocumentTitleuseDebounce 这两个给 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/1/kanban

因此我们通过正则表达式来获取出当中的数字也就是我们的 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 }))
}

在这里就讲讲类型吧~

在这里我们接收一个可选的参数,TaskTask 是我们封装在 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. 看板和任务数据统一

明确我们这个组件的作用,我们需要用它来渲染每一列的看板

Hooks + TS 搭建一个任务管理系统(六)-- 看板页面展示_第1张图片

大概是这样一个布局,首先,因为我们需要将任务渲染到对应的看板列表下,因此首先我们需要解决数据的问题

我们在 KanbanColumn 中获取数据,在这里我们需要十分明确,这个我们的这个组件它只是渲染一列,我们通过遍历实现多列,这个很关键

我们在 column 中获取所有的 task 数据,通过 filter 方法,将它筛选出来,这样,最后得到的就是和 kanbanId 匹配的 task 数据

const {
      data: allTasks } = useTasks(useTasksSearchParams())
// 对数据进行分类,返回的是三段数据,都是数组
const tasks = allTasks?.filter(task => task.kanbanId === kanban.id)

在这里有一个很有意思的问题

我们个每一个 column 都绑定了一个 useTasks ,按理说它应该会发送多次的请求 ,我们来看看到底是不是这样

Hooks + TS 搭建一个任务管理系统(六)-- 看板页面展示_第2张图片

在这里我们可以发现它一共发送了 2次请求,但是我启动的这个看板中有三个 column

不妨我们再多添加几个 column ,我们再来看看

Hooks + TS 搭建一个任务管理系统(六)-- 看板页面展示_第3张图片

在这里始终都是只有2个请求,那这是为什么呢?

其实在我们在遍历添加 kanbanColumns 组件时,只会发起一个请求,即使,我们给每一个 column 都绑定了 useTask

这是因为,我们采用的 react-query 的功劳,在我们采用 useQuery 时,如果在 2s 之内有相同的 queryKey 发出请求的话,就会合并这些请求,只会发出一个

现在我们已经有了每个看板下的 Task 数据了,我们只需要遍历渲染即可,这里我们采用的还是 Antd 组件库

2. useTaskTypes 处理不同类型任务的 icon

在我们的任务中又分为 bugtask,我们都会有相应的图标展示

在这里我们在 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()
}

总结

在这篇文章中我们做完了看板页面的制作,我们能学到这些东西

  1. 熟悉了增删改查的操作
  2. 了解了 useQuery 的用法
  3. modal 组件有了更多的了解
  4. 了解了 react-query 能够优化请求次数

最后,可能在很多地方讲诉的不够清晰,请见谅

如果文章有什么错误的地方,或者有什么疑问,欢迎留言,也欢迎私信交流

你可能感兴趣的:(从零搭建一个任务管理系统,前端,typescript,javascript,react.js,前端框架)