[Actual Combat] Eleven. Kanban page and task group page development (1) —— React17+React Hook+TS4 best practice, imitating Jira enterprise-level projects (23)


Source of learning content: React + React Hook + TS Best Practice - MOOC


Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):

item Version
react & react-dom ^18.2.0
react-router & react-router-dom ^6.11.2
antd ^4.24.8
@commitlint/cli & @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
husky ^8.0.3
lint-staged ^13.1.2
prettier 2.8.4
json-server 0.17.2
craco-less ^2.0.0
@craco/craco ^7.1.0
qs ^6.11.0
dayjs ^1.11.7
react-helmet ^6.1.0
@types/react-helmet ^6.1.6
react-query ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/react & @emotion/styled ^11.10.6

The specific configuration, operation and content will be different, and the "pit" will also be different. . .


1. Project launch: project initialization and configuration

2. React and Hook application: implement the project list

3. TS Application: JS God Assist - Strong Type

4. JWT, user authentication and asynchronous request


5. CSS is actually very simple - add styles with CSS-in-JS


6. User experience optimization - loading and error state handling



7. Hook, routing, and URL state management



8. User selector and item editing function


9. In-depth React state management and Redux mechanism





10. Use react-query to get data and manage cache


11. Kanban page and task group page development

1. Preparatory work for Kanban list development

There is a small problem with the routing of the previous project details into the Kanban page. Clicking the back button of the browser cannot go back. The reasons are as follows:

  • The route list is a stack structure. Every time a route is accessed push, a new route will be entered. When you click Return, the previous route will be placed on the top of the stack; and when entering the project details page (from 'projects'to 'projects/1'), the default redirection sub-route is the kanban page ( projects/1/viewboard) , when returning to the previous route, it will redirect to the Kanban page route by default. An example of a list stack is as follows:
  • ['projects', 'projects/1', 'projects/1/viewboard']

Next, to solve this problem, edit src\screens\ProjectDetail\index.tsx(add new attributes to the redirect tag replace, directly replace the original route when redirecting):

...
export const ProjectDetail = () => {
    
    
  return (
    <div>
      ...
      <Routes>
        ...
        <Route index element={
    
    <Navigate to="viewboard" replace/>} />
      </Routes>
    </div>
  );
};

In order to facilitate the unified calling of subsequent types, extract中 into src\screens\ProjectList\components\List.tsxthe directoryinterface Projectsrc\types

In the video, WebStorm is used, and the blogger uses VSCode:

  • Right-click on the variable that needs to be refactored, select Refactor (shortcut key Ctrl+ Shift+ R), select Move to a new file, the default file with the same variable name will be created in the same level directory as the current file, and other reference locations will also be changed accordingly, involving reference locations:
    • src\utils\project.ts
    • src\screens\ProjectList\components\SearchPanel.tsx
    • src\screens\ProjectList\components\List.tsx
  • Drag the newly generated file to src\typesthe directory, and you can see that other reference locations have also changed accordingly

src\screens\ProjectList\components\SearchPanel.tsxin interface Userdoes the same, involving quoting locations:

  • src\screens\ProjectList\components\SearchPanel.tsx
  • src\screens\ProjectList\components\List.tsx
  • src\auth-provider.ts
  • src\context\auth-context.tsx
  • src\utils\use-users.ts

The Kanban page also needs the following two types, create a new one:

  • src\types\Viewboard.ts:
export interface Viewboard {
    
    
  id: number;
  name: string;
  projectId: number;
}
  • src\types\Task.ts
export interface Task {
    
    
  id: number;
  name: string;
  projectId: number;
  processorId: number; // 经办人
  taskGroupId: number; // 任务组
  kanbanId: number;
  typeId: number;      // bug or task
  note: string;
}

Next create a hook for the data request:

src\utils\viewboard.ts:

import {
    
     cleanObject } from "utils";
import {
    
     useHttp } from "./http";
import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useQuery } from "react-query";

export const useViewboards = (param?: Partial<Viewboard>) => {
    
    
  const client = useHttp();

  return useQuery<Viewboard[]>(["viewboards", param], () =>
    client("kanbans", {
    
     data: cleanObject(param || {
    
    }) })
  );
};

src\utils\task.ts:

import {
    
     cleanObject } from "utils";
import {
    
     useHttp } from "./http";
import {
    
     Task } from "types/Task";
import {
    
     useQuery } from "react-query";

export const useTasks = (param?: Partial<Task>) => {
    
    
  const client = useHttp();

  return useQuery<Task[]>(["tasks", param], () =>
    client("tasks", {
    
     data: cleanObject(param || {
    
    }) })
  );
};

2. Preliminary development of Kanban list

Next, start to develop the kanban list to display the project data that needs to be used. You can extract one from urlGet projectId, and then use idto get the project datahook

New src\screens\ViewBoard\utils.ts:

import {
    
     useLocation } from "react-router"
import {
    
     useProject } from "utils/project"

export const useProjectIdInUrl = () => {
    
    
  const {
    
     pathname } = useLocation()
  const id = pathname.match(/projects\/(\d+)/)?.[1]
  return Number(id)
}

export const useProjectInUrl = () => useProject(useProjectIdInUrl())

export const useViewBoardSearchParams = () => ({
    
    projectId: useProjectIdInUrl()})

export const useViewBoardQueryKey = () => ['viewboards', useViewBoardSearchParams()]

export const useTasksSearchParams = () => ({
    
    projectId: useProjectIdInUrl()})

export const useTasksQueryKey = () => ['tasks', useTasksSearchParams()]

Note: Each must ensure that the first item of the return value is consistent with the first parameter in the subsequent useXXXQueryKeylist request , otherwise subsequent additions, deletions, and modifications will not be able to automatically re-request the list, and troubleshooting is difficultuseXXXuseQuery

Customize a display column component (task list) for Kanban for each type to use

New src\screens\ViewBoard\components\ViewboardCloumn.tsx:

import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useTasks } from "utils/task";
import {
    
     useTasksSearchParams } from "../utils";

export const ViewboardColumn = ({
     
     viewboard}:{
     
     viewboard: Viewboard}) => {
    
    
  const {
    
     data: allTasks } = useTasks(useTasksSearchParams())
  const tasks = allTasks?.filter(task => task.kanbanId === viewboard.id)
  return <div>
    <h3>{
    
    viewboard.name}</h3>
    {
    
    
      tasks?.map(task => <div key={
    
    task.id}>{
    
    task.name}</div>)
    }
  </div>
}

EDIT src\screens\ViewBoard\index.tsx:

import {
    
     useDocumentTitle } from "utils";
import {
    
     useViewboards } from "utils/viewboard";
import {
    
     useProjectInUrl, useViewBoardSearchParams } from "./utils";
import {
    
     ViewboardColumn } from "./components/ViewboardCloumn"
import styled from "@emotion/styled";

export const ViewBoard = () => {
    
    
  useDocumentTitle('看板列表')

  const {
    
    data: currentProject} = useProjectInUrl()
  const {
    
    data: viewboards, } = useViewboards(useViewBoardSearchParams())

  return <div>
    <h1>{
    
    currentProject?.name}看板</h1>
    <ColumnsContainer>
    {
    
    
      viewboards?.map(vbd => <ViewboardColumn viewboard={
    
    vbd} key={
    
    vbd.id}/>)
    }
    </ColumnsContainer>
  </div>;
};

const ColumnsContainer = styled.div`
  display: flex;
  overflow: hidden;
  margin-right: 2rem;
`

It can be seen from the code: ViewboardColumn is rendered multiple times after viewboards.map, and useTasks is also executed multiple times at the same time. However, if you look closely at the browser developer tools, you can find that the corresponding request is not executed multiple times, but only once. This is because The caching mechanism of react-query (by default, multiple requests with the same key and the same parameters sent within two seconds are only executed for the last time)

Visit the kanban list and you can see the following content, and the horizontal arrangement of the three status tasks is normal:

待完成
管理登录界面开发

开发中
管理注册界面开发
权限管理界面开发
UI开发
自测

已完成
单元测试
性能优化

3. Add task, bug icon

The task type interface does not return directly, but only returns a typeId, which cannot clearly identify the task type, and requires a separate access interface to obtain the specific task type

New src\types\TaskType.ts:

export interface TaskType {
    
    
  id: number;
  name: string;
}

New src\utils\task-type.ts:

import {
    
     useHttp } from "./http";
import {
    
     useQuery } from "react-query";
import {
    
     TaskType } from "types/TaskType";

export const useTaskTypes = () => {
    
    
  const client = useHttp();

  return useQuery<TaskType[]>(["taskTypes"], () =>
    client("tasks")
  );
};

Copy the following two svg files tosrc\assets

bug.svg
insert image description here

<svg xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xlinkHref="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>bug</title>
    <desc>Created with Sketch.</desc>
    <defs/>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
        <g id="bug" sketch:type="MSArtboardGroup">
            <g id="Bug" sketch:type="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#E5493A" sketch:type="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"/>
                <path d="M10,7 C10,8.657 8.657,10 7,10 C5.343,10 4,8.657 4,7 C4,5.343 5.343,4 7,4 C8.657,4 10,5.343 10,7" id="Fill-2" fill="#FFFFFF" sketch:type="MSShapeGroup"/>
            </g>
        </g>
    </g>
</svg>

task.svg
insert image description here

<svg xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>task</title>
    <desc>Created with Sketch.</desc>
    <defs/>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
        <g id="task" sketch:type="MSArtboardGroup">
            <g id="Task" sketch:type="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#4BADE8" sketch:type="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"/>
                <g id="Page-1" transform="translate(4.000000, 4.500000)" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" sketch:type="MSShapeGroup">
                    <path d="M2,5 L6,0" id="Stroke-1"/>
                    <path d="M2,5 L0,3" id="Stroke-3"/>
                </g>
            </g>
        </g>
    </g>
</svg>

Direct use may result in the following error:

Compiled with problems:X

ERROR in ./src/assets/task.svg

Module build failed (from ./node_modules/@svgr/webpack/lib/index.js):
SyntaxError: unknown file: Namespace tags are not supported by default. React's JSX doesn't support namespace tags. You can set `throwIfNamespace: false` to bypass this warning.

Change skety:typethe tag attribute of this type to sketchTypecamelCase so that it is JSXaccepted.

svgThe modified source code of the source file is as follows:

  • bug.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xlinkHref="http://www.w3.org/1999/xlink" xmlnsSketch="http://www.bohemiancoding.com/sketch/ns">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>bug</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketchType="MSPage">
        <g id="bug" sketchType="MSArtboardGroup">
            <g id="Bug" sketchType="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#E5493A" sketchType="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"></rect>
                <path d="M10,7 C10,8.657 8.657,10 7,10 C5.343,10 4,8.657 4,7 C4,5.343 5.343,4 7,4 C8.657,4 10,5.343 10,7" id="Fill-2" fill="#FFFFFF" sketchType="MSShapeGroup"></path>
            </g>
        </g>
    </g>
</svg>
  • task.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg"
     xmlnsSketch="http://www.bohemiancoding.com/sketch/ns">
    <!-- Generator: Sketch 3.5.2 (25235) - http://www.bohemiancoding.com/sketch -->
    <title>task</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketchType="MSPage">
        <g id="task" sketchType="MSArtboardGroup">
            <g id="Task" sketchType="MSLayerGroup" transform="translate(1.000000, 1.000000)">
                <rect id="Rectangle-36" fill="#4BADE8" sketchType="MSShapeGroup" x="0" y="0" width="14" height="14" rx="2"></rect>
                <g id="Page-1" transform="translate(4.000000, 4.500000)" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" sketchType="MSShapeGroup">
                    <path d="M2,5 L6,0" id="Stroke-1"></path>
                    <path d="M2,5 L0,3" id="Stroke-3"></path>
                </g>
            </g>
        </g>
    </g>
</svg>

EDIT src\screens\ViewBoard\components\ViewboardCloumn.tsx(introduced icons, and beautified):

import {
    
     Viewboard } from "types/Viewboard";
import {
    
     useTasks } from "utils/task";
import {
    
     useTasksSearchParams } from "../utils";
import {
    
     useTaskTypes } from "utils/task-type";
import taskIcon from "assets/task.svg";
import bugIcon from "assets/bug.svg";
import styled from "@emotion/styled";
import {
    
     Card } from "antd";

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} />;
};

export const ViewboardColumn = ({
     
      viewboard }: {
     
      viewboard: Viewboard }) => {
    
    
  const {
    
     data: allTasks } = useTasks(useTasksSearchParams());
  const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);
  return (
    <Container>
      <h3>{
    
    viewboard.name}</h3>
      <TasksContainer>
        {
    
    tasks?.map((task) => (
          <Card style={
    
    {
    
    marginBottom: '0.5rem'}} key={
    
    task.id}>
            <div>{
    
    task.name}</div>
            <TaskTypeIcon id={
    
    task.id} />
          </Card>
        ))}
      </TasksContainer>
    </Container>
  );
};

export const Container = styled.div`
  min-width: 27rem;
  border-radius: 6px;
  background-color: rgb(244, 245, 247);
  display: flex;
  flex-direction: column;
  padding: .7rem .7rem 1rem;
  margin-right: 1.5rem;
`

const TasksContainer = styled.div`
  overflow: scroll;
  flex: 1;
  ::-webkit-scrollbar {
    display: none;
  }
`

View the effect:
renderings


Some reference notes are still in draft stage, so stay tuned. . .

Guess you like

Origin blog.csdn.net/qq_32682301/article/details/132321120