大家好,我是小丞同學,一名大二的前端愛好者
這個系列文章是實戰 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 能夠優化請求次數