文章目录

一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook,路由,与 URL 状态管理八、用户选择器与项目编辑功能九、深入React 状态管理与Redux机制十、用 react-query 获取数据,管理缓存十一、看板页面及任务组页面开发1.看板列表开发准备工作2.看板列表初步开发3.添加task, bug 图标

学习内容来源:React + React Hook + TS 最佳实践-慕课网

相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:

项版本react & react-dom^18.2.0react-router & react-router-dom^6.11.2antd^4.24.8@commitlint/cli & @commitlint/config-conventional^17.4.4eslint-config-prettier^8.6.0husky^8.0.3lint-staged^13.1.2prettier2.8.4json-server0.17.2craco-less^2.0.0@craco/craco^7.1.0qs^6.11.0dayjs^1.11.7react-helmet^6.1.0@types/react-helmet^6.1.6react-query^6.1.0@welldone-software/why-did-you-render^7.0.1@emotion/react & @emotion/styled^11.10.6

具体配置、操作和内容会有差异,“坑”也会有所不同。。。

一、项目起航:项目初始化与配置

一、项目起航:项目初始化与配置

二、React 与 Hook 应用:实现项目列表

二、React 与 Hook 应用:实现项目列表

三、TS 应用:JS神助攻 - 强类型

三、 TS 应用:JS神助攻 - 强类型

四、JWT、用户认证与异步请求

四、 JWT、用户认证与异步请求(上)

四、 JWT、用户认证与异步请求(下)

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)

六、用户体验优化 - 加载中和错误状态处理

六、用户体验优化 - 加载中和错误状态处理(上)

六、用户体验优化 - 加载中和错误状态处理(中)

六、用户体验优化 - 加载中和错误状态处理(下)

七、Hook,路由,与 URL 状态管理

七、Hook,路由,与 URL 状态管理(上)

七、Hook,路由,与 URL 状态管理(中)

七、Hook,路由,与 URL 状态管理(下)

八、用户选择器与项目编辑功能

八、用户选择器与项目编辑功能(上)

八、用户选择器与项目编辑功能(下)

九、深入React 状态管理与Redux机制

九、深入React 状态管理与Redux机制(一)

九、深入React 状态管理与Redux机制(二)

九、深入React 状态管理与Redux机制(三)

九、深入React 状态管理与Redux机制(四)

九、深入React 状态管理与Redux机制(五)

十、用 react-query 获取数据,管理缓存

十、用 react-query 获取数据,管理缓存(上)

十、用 react-query 获取数据,管理缓存(下)

十一、看板页面及任务组页面开发

1.看板列表开发准备工作

之前的项目详情进入看板页的路由有个小问题,点击浏览器返回按钮回不去,原因如下:

路由列表是栈结构,每访问一个路由都会 push 一个新路由进去,当点击返回,就会将上一个路由置于栈顶;而进入项目详情页(从'projects'到'projects/1')默认重定向子路由是看板页(projects/1/viewboard),返回上一个路由时,默认又会重定向到看板页路由。列表栈示例如下:['projects', 'projects/1', 'projects/1/viewboard']

接下来解决一下这个问题,编辑 src\screens\ProjectDetail\index.tsx (重定向标签新增属性 replace,在重定向时直接替换原路由):

...

export const ProjectDetail = () => {

return (

...

...

} />

);

};

为了方便后续类型统一调用,将 src\screens\ProjectList\components\List.tsx 中 interface Project 提取到 src\types 目录下

视频中 是用 WebStorm ,博主用的是 VSCode:

在需要重构的变量上右击,选择重构(快捷键 Ctrl + Shift + R),选择 Move to a new file,默认同变量名的文件会创建在当前文件所在同一级目录下,其他引用位置也相应改变,涉及引用位置:

src\utils\project.tssrc\screens\ProjectList\components\SearchPanel.tsxsrc\screens\ProjectList\components\List.tsx 拖动新生成的文件到 src\types 目录下,可以看到其他引用位置也相应改变

相关功能文档:TypeScript Programming with Visual Studio Code

src\screens\ProjectList\components\SearchPanel.tsx 中 interface User 也执行同样操作,涉及引用位置:

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

看板页还需要以下两个类型,新建一下:

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;

}

接下来创建数据请求的 hook:

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) => {

const client = useHttp();

return useQuery(["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) => {

const client = useHttp();

return useQuery(["tasks", param], () =>

client("tasks", { data: cleanObject(param || {}) })

);

};

2.看板列表初步开发

接下来开始开发看板列表,展示需要用到项目数据,可以提取一个从 url 获取 projectId,再用 id 获取项目数据的 hook

新建 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()]

注意:每一个 useXXXQueryKey 都要确保返回值第一项 与后续列表请求 useXXX 中 useQuery 的第一个参数保持一致,否则后续增删改都无法正常自动重新请求列表,问题排查比较困难

为看板定制一个展示列组件(任务列表),供每个类型来使用

新建 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

{viewboard.name}

{

tasks?.map(task =>

{task.name}
)

}

}

编辑 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

{currentProject?.name}看板

{

viewboards?.map(vbd => )

}

;

};

const ColumnsContainer = styled.div`

display: flex;

overflow: hidden;

margin-right: 2rem;

`

通过代码可知:viewboards.map 后 ViewboardColumn 渲染多次,其中 useTasks 也同时执行多次,但是仔细看浏览器开发者工具可发现,相应请求并没有执行多次,而是只执行了一次,这是因为 react-query 的缓存机制(默认两秒内发送的多个key相同且的参数相同的请求只执行最后一次)

访问看板列表可看到如下内容且三种状态任务横向排列即为正常:

待完成

管理登录界面开发

开发中

管理注册界面开发

权限管理界面开发

UI开发

自测

已完成

单元测试

性能优化

3.添加task, bug 图标

任务的类型接口并不直接返回,而是只返回一个 typeId,并不能明确标识任务类型,需要单独访问接口来获取具体任务类型

新建 src\types\TaskType.ts:

export interface TaskType {

id: number;

name: string;

}

新建 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(["taskTypes"], () =>

client("tasks")

);

};

将以下两个 svg 文件拷贝到 src\assets

bug.svg

bug

Created with Sketch.

task.svg

task

Created with Sketch.

直接使用可能会有如下报错:

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.

把 skety:type 这种类型的标签属性改成 sketchType 驼峰这样才能被 JSX 接受。

编译有问题: ./src/assets/bug.svg 中的错误-慕课网reactjs - SyntaxError: unknown: Namespace tags are not supported by default - Stack Overflow

源 svg 文件 修改后的源码如下:

bug.svg

bug

Created with Sketch.

task.svg

xmlnsSketch="http://www.bohemiancoding.com/sketch/ns">

task

Created with Sketch.

编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx(引入图标,并美化):

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 react.js 前端 前端框架 【实战】十一、看板页面及任务组页面开发(一) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十三)  第1张;

};

export const ViewboardColumn = ({ viewboard }: { viewboard: Viewboard }) => {

const { data: allTasks } = useTasks(useTasksSearchParams());

const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);

return (

{viewboard.name}

{tasks?.map((task) => (

{task.name}

))}

);

};

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;

}

`

查看效果:

部分引用笔记还在草稿阶段,敬请期待。。。

推荐阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。