【实战】十一、看板页面及任务组页面开发(四) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十六)
文章目录
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、TS 应用:JS神助攻 - 强类型
- 四、JWT、用户认证与异步请求
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
- 六、用户体验优化 - 加载中和错误状态处理
- 七、Hook,路由,与 URL 状态管理
- 八、用户选择器与项目编辑功能
- 九、深入React 状态管理与Redux机制
- 十、用 react-query 获取数据,管理缓存
- 十一、看板页面及任务组页面开发
- 1~3
- 4~6
- 7&8
- 9.拖拽实现
- 10.拖拽持久化
学习内容来源:React + React Hook + TS 最佳实践-慕课网
相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:
| 项 | 版本 |
|---|---|
| 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 |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
一、项目起航:项目初始化与配置
- 一、项目起航:项目初始化与配置
二、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~3
- 十一、看板页面及任务组页面开发(一)
4~6
- 十一、看板页面及任务组页面开发(二)
7&8
- 十一、看板页面及任务组页面开发(三)
9.拖拽实现
接下来的内容将会是整个课程最难的部分,相关知识也不是很常用
- 安装拖拽功能库
react-beautiful-dnd及相关类型文件库
npm i react-beautiful-dnd ## --force
npm i @types/react-beautiful-dnd -D ## --force
接下来在原功能库的基础上自行二次封装
新建 src\components\grag-and-drop.tsx:
import React, { ReactNode } from "react"
import { Draggable, DraggableProps, Droppable, DroppableProps, DroppableProvided, DroppableProvidedProps } from "react-beautiful-dnd"// 删除原有 “函数类型” children,使用 ReactNode 类型的 children
type DropProps = Omit<DroppableProps, 'children'> & { children: ReactNode }export const Drop = ({ children, ...props }: DropProps) => {return (<Droppable {...props}>{(provided) => {if (React.isValidElement(children)) {return React.cloneElement(children, {...provided.droppableProps,ref: provided.innerRef,provided,});}return <div />;}}</Droppable>);
};type DropChildProps =Partial<{ provided: DroppableProvided } & DroppableProvidedProps> &React.HTMLAttributes<HTMLDivElement>export const DropChild = React.forwardRef<HTMLDivElement, DropChildProps>(({children, ...props}, ref) => <div ref={ref} {...props} >{children}{props.provided?.placeholder}</div>
);type DragProps = Omit<DraggableProps, 'children'> & { children: ReactNode }
export const Drag = ({children, ...props}: DragProps) => {return <Draggable {...props}>{(provided => {if(React.isValidElement(children)) {return React.cloneElement(children, {...provided.draggableProps,...provided.dragHandleProps,ref: provided.innerRef})}return <div/>})}</Draggable>
}
forwardRef 是用作转发的,经过包裹后的组件可以传入 ref 属性
- Refs 转发 – React
这步报错:ref: provided.innerRef
Argument of type '{ ref: (element: HTMLElement | null) => void; 'data-rbd-drag-handle-draggable-id'?: string | undefined; 'data-rbd-drag-handle-context-id'?: string | undefined; 'aria-describedby'?: string | undefined; ... 7 more ...; onTransitionEnd?: React.TransitionEventHandler<...> | undefined; }' is not assignable to parameter of type 'Partial<unknown> & Attributes'.Object literal may only specify known properties, and 'ref' does not exist in type 'Partial<unknown> & Attributes'.
接下来使用这个组件
编辑 src\screens\ViewBoard\index.tsx:
...
import { DragDropContext } from "react-beautiful-dnd";
import { Drag, Drop, DropChild } from "components/grag-and-drop";export const ViewBoard = () => {useDocumentTitle("看板列表");const { data: currentProject } = useProjectInUrl();const { data: viewboards, isLoading: viewBoardIsLoading } = useViewboards(useViewBoardSearchParams());const { isLoading: taskIsLoading } = useTasks(useTasksSearchParams());const isLoading = taskIsLoading || viewBoardIsLoading;return (<DragDropContext onDragEnd={() => {}}><ViewContainer><h1>{currentProject?.name}看板</h1><SearchPanel />{isLoading ? (<Spin />) : (<Drop type='COLUMN' direction='horizontal' droppableId="viewboard"><ColumnsContainer>{viewboards?.map((vbd, index) => (<Drag key={vbd.id} draggableId={'viewboard' + vbd.id} index={index}><ViewboardColumn viewboard={vbd} key={vbd.id} /></Drag>))}<CreateViewBoard /></ColumnsContainer></Drop>)}<TaskModal /></ViewContainer></DragDropContext>);
};export const ColumnsContainer = styled(DropChild)`display: flex;overflow-x: scroll;flex: 1;
`;
编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx 使组件可以透传 props 以及通过 forwardRef 转发 传入 ref:
...
export const ViewboardColumn = React.forwardRef<HTMLDivElement, { viewboard: Viewboard }>(({ viewboard, ...props }, ref) => {const { data: allTasks } = useTasks(useTasksSearchParams());const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);return (<Container {...props} ref={ref}><Row><h3>{viewboard.name}</h3><More viewboard={viewboard} key={viewboard.id}/></Row><TasksContainer>{tasks?.map((task) => (<TaskCard key={task.id} task={task} />))}<CreateTask kanbanId={viewboard.id} /></TasksContainer></Container>);
});
10.拖拽持久化
拖拽的时候 看板之间的间隔应该是不变的
编辑 src\screens\ViewBoard\index.tsx(调整组件层级并显式使用 DropChild):
...
export const ViewBoard = () => {...return (<DragDropContext onDragEnd={() => {}}><ViewContainer><h1>{currentProject?.name}看板</h1><SearchPanel />{isLoading ? (<Spin />) : (<ColumnsContainer><Drop type="COLUMN" direction="horizontal" droppableId="viewboard"><DropChild style={{display: 'flex'}}>{viewboards?.map((vbd, index) => (<Dragkey={vbd.id}draggableId={"viewboard" + vbd.id}index={index}><ViewboardColumn viewboard={vbd} key={vbd.id} /></Drag>))}</DropChild></Drop><CreateViewBoard /></ColumnsContainer>)}<TaskModal /></ViewContainer></DragDropContext>);
};export const ColumnsContainer = styled.div`display: flex;overflow-x: scroll;flex: 1;
`;
接下来做 任务拖拽排序
编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx:
...
import { Drag, Drop, DropChild } from "components/grag-and-drop";
...
export const ViewboardColumn = React.forwardRef<HTMLDivElement,{ viewboard: Viewboard }
>(({ viewboard, ...props }, ref) => {const { data: allTasks } = useTasks(useTasksSearchParams());const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);return (<Container {...props} ref={ref}><Row><h3>{viewboard.name}</h3><More viewboard={viewboard} key={viewboard.id} /></Row><TasksContainer><Drop type="Row" direction="vertical" droppableId={'task' + viewboard.id}><DropChild>{tasks?.map((task, taskIndex) => (<Dragkey={task.id}draggableId={"task" + task.id}index={taskIndex}><TaskCard key={task.id} task={task} /></Drag>))}</DropChild></Drop><CreateTask kanbanId={viewboard.id} /></TasksContainer></Container>);
});
拖拽功能好了,接下来将拖拽结果持久化到数据库中
编辑 src\utils\use-optimistic-options.ts(看板和任务排序 获取URL参数,为后续乐观更新做准备):
...
export const useReorderViewboardConfig = (queryKey: QueryKey) =>useConfig(queryKey, (target, old) => old ? [old, ...target] : []);export const useReorderTaskConfig = (queryKey: QueryKey) =>useConfig(queryKey, (target, old) => old || []);
编辑 src\utils\viewboard.ts(新增看板排序接口的 Custom Hook, SortProps 与 useReorderTask 共用):
...
export interface SortProps {// 要重新排序的 itemfromId: number;// 目标 itemreferenceId: number;// 放在目标 Item 的前还是后type: 'before' | 'after';fromKanbanId?: number;toKanbanId?: number;
}export const useReorderViewboard = () => {const client = useHttp();return useMutation((params: SortProps) => {return client("kanbans/reorder", {data: params,method: "POST",});}, useReorderViewboardConfig(queryKey));
};
编辑 src\utils\task.ts(新增看板排序接口的 Custom Hook):
...
export const useReorderTask = (queryKey: QueryKey) => {const client = useHttp();return useMutation((params: SortProps) => {return client("tasks/reorder", {data: params,method: "POST",});}, useReorderTaskConfig(queryKey));
};
编辑 src\screens\ViewBoard\index.tsx(完善之前预留的 onDragEnd):
...
import { useReorderViewboard, useViewboards } from "utils/viewboard";
import {useProjectInUrl,useTasksQueryKey,useTasksSearchParams,useViewBoardQueryKey,useViewBoardSearchParams,
} from "./utils";
...
import { useReorderTask, useTasks } from "utils/task";
...
import { useCallback } from "react";export const ViewBoard = () => {...const onDragEnd = useDragEnd();return (<DragDropContext onDragEnd={onDragEnd}>...</DragDropContext>);
};export const useDragEnd = () => {const { data: viewboards } = useViewboards(useViewBoardSearchParams());const { mutate: reorderViewBoard } = useReorderViewboard(useViewBoardQueryKey());const { mutate: reorderTask } = useReorderTask(useTasksQueryKey());const { data: allTasks = [] } = useTasks(useTasksSearchParams());return useCallback(({ source, destination, type }: DropResult) => {if (!destination) {return;}// 看板排序if (type === "COLUMN") {const fromId = viewboards?.[source.index].id;const toId = viewboards?.[destination.index].id;if (!fromId || !toId || fromId === toId) {return;}const type = destination.index > source.index ? "after" : "before";reorderViewBoard({ fromId, referenceId: toId, type });}if (type === "ROW") {const fromKanbanId = +source.droppableId;const toKanbanId = +destination.droppableId;if (fromKanbanId === toKanbanId) {return;}const fromTask = allTasks.filter((task) => task.kanbanId === fromKanbanId)[source.index];const toTask = allTasks.filter((task) => task.kanbanId === toKanbanId)[destination.index];if (fromTask?.id === toTask?.id) {return;}reorderTask({fromId: fromTask?.id,referenceId: toTask?.id,fromKanbanId,toKanbanId,type:fromKanbanId === toKanbanId && destination.index > source.index? "after": "before",});}},[viewboards, reorderViewBoard, allTasks, reorderTask]);
};
...
至此,拖拽持久化完成,查看效果验证
部分引用笔记还在草稿阶段,敬请期待。。。
相关文章:
【实战】十一、看板页面及任务组页面开发(四) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十六)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...
解决docker无法执行定时任务问题
背景 在docker里面想创建定时任务,但是发现时间到了并没有执行,第一时间想到应该是没有开启crond服务,然后执行systemctl status crond.service报错如下所示: System has not been booted with systemd as init system (PID 1).…...
【FreeRTOS】【STM32】中断详细介绍
文章目录 一、三种优先级的概念辨析1. 先理清楚两个概念:CPU 和 MPU2. Cortex-M3 内核与 STM32F1XX 控制器有什么关系3. 优先级的概念辨析① Cortex-M3 内核和 STM32F1XX 的中断优先级② FreeRTOS 的任务的优先级 二、 Cortex-M3 内核的中断优先级1. 中断编号2. 优先…...
stm32串口通信(PC--stm32;中断接收方式;附proteus电路图;开发方式:cubeMX)
单片机型号STM32F103R6: 最后实现的效果是,开机后PC内要求输入1或0,输入1则打开灯泡,输入0则关闭灯泡,输入其他内容则显示错误,值得注意的是这个模拟的东西只能输入英文 之所以用2个LED灯是因为LED电阻粗略一算就是1…...
计算机毕设 基于机器学习与大数据的糖尿病预测
文章目录 1 课题背景2 数据导入处理3 数据可视化分析4 特征选择4.1 通过相关性进行筛选4.2 多重共线性4.3 RFE(递归特征消除法)4.4 正则化 5 机器学习模型建立与评价5.1 评价方式的选择5.2 模型的建立与评价5.3 模型参数调优5.4 将调参过后的模型重新进行…...
【数据结构】——查找、散列表的相关习题
目录 一、选择填空判断题题型一(顺序、二分查找的概念)题型二(分块查找的概念)题型三(关键字比较次数) 二、应用题题型一(二分查找判定树) 一、选择填空判断题 题型一(顺…...
提升Java开发效率:掌握HashMap的常见方法与基本原理
文章目录 前言一、概述1. 认识HashMap2. HashMap 的作用和重要性3. 简要讲解 HashMap 的基本原理和实现方式 二、了解 HashMap 创建及其的常见操作方法1. HashMap的创建2. 添加元素 put()3. 访问元素 get()4. 删除元素 remove()5. 计算大小 size()6. 迭代 HashMap for-each7.判…...
PostgreSQL系统概述
目录 写在前面 1.简介 1.1何为关系型数据库 1.2何为对象型数据库 2.特性 3.代码结构 3.1数据库集簇 3.2Parser查询分析流程 3.3内部查询树组成部分 3.3.1目标列表 3.4Optimizer查询优化流程 3.4.1查询计划 3.5非计划查询的SQL命令 写在前面 如有错误请指正…...
掌握AI助手的魔法工具:解密Prompt(提示)在AIGC时代的应用「中篇」
文章目录 掌握AI助手的魔法工具:解密Prompt(提示)在AIGC时代的应用「中篇」一、指南原则1: 使用明确和具体的指令原则2: 给模型思考的时间 二、迭代三、总结与提取四、局限与改善五、总结 掌握AI助手的魔法工具:解密Prompt&#x…...
git svn:使用 git 命令来管理 svn 仓库
git-svn 使用教程 参考以下: https://cloud.tencent.com/developer/article/1415892 # 在SVN仓库上使用Git 源 https://blog.csdn.net/jiejie11080/article/details/106917116 # git svn clone速度慢的解决办法 http://blog.chinaunix.net/uid-11639156-id-30774…...
软考高级系统架构设计师系列论文九十一:论分布式数据库的设计与实现
软考高级系统架构设计师系列论文九十一:论分布式数据库的设计与实现 一、分布式数据库相关知识点二、摘要三、正文四、总结一、分布式数据库相关知识点 软考高级系统架构设计师系列之:分布式存储技术...
GeoHash之存储篇
前言: 在上一篇文章GeoHash——滴滴打车如何找出方圆一千米内的乘客主要介绍了GeoHash的应用是如何的,本篇文章我想要带大家探索一下使用什么样的数据结构去存储这些Base32编码的经纬度能够节省内存并且提高查询的效率。 前缀树、跳表介绍: …...
后端项目开发:集成接口文档(swagger-ui)
swagger集成文档具有功能丰富、及时更新、整合简单,内嵌于应用的特点。 由于后台管理和前台接口均需要接口文档,所以在工具包构建BaseSwaggerConfig基类。 1.引入依赖 <dependency><groupId>io.springfox</groupId><artifactId>…...
代码随想录训练营29天|●* 491.递增子序列 * 46.全排列 * 47.全排列 II
class Solution {vector<vector<int>>res;vector<int>vec;void backing(vector<int>& nums,int index){if(vec.size()>2&&is(vec)){res.push_back(vec);}unordered_set<int> uset; // 使用set对本层元素进行去重for(int iindex;i…...
uniapp日期选择组件优化
<uni-forms-item label="出生年月" name="birthDate"><view style="display: flex;flex-direction: row;align-items: center;height: 100%;"><view class="" v-...
AI驱动的大数据创新:探索软件开发中的机会和挑战
文章目录 机会数据驱动的决策自动化和效率提升智能预测和优化个性化体验 挑战数据隐私与安全技术复杂性数据质量和清洗伦理和社会问题 案例:智能代码生成工具总结 🎈个人主页:程序员 小侯 🎐CSDN新晋作者 🎉欢迎 &…...
国产化-银河麒麟V10系统及docker的安装
一、最近在研究国产化操作系统,“银河麒麟V10”, 在我电脑本机vmware 15的虚拟机中进行安装测试; 1.点击这里提交产品试用申请,不过只需要随便输入,手机号验证码验证后方可跳转至下载地址产品试用申请国产操作系统、银…...
计算机毕设 基于机器视觉的二维码识别检测 - opencv 二维码 识别检测 机器视觉
文章目录 0 简介1 二维码检测2 算法实现流程3 特征提取4 特征分类5 后处理6 代码实现5 最后 0 简介 今天学长向大家介绍一个机器视觉的毕设项目,二维码 / 条形码检测与识别 基于机器学习的二维码识别检测 - opencv 二维码 识别检测 机器视觉 1 二维码检测 物体检…...
Redis原理剖析
一、Redis简介 Redis是一个开源的,基于网络的,高性能的key-value数据库,弥补了memcached这类key-value存储的不足,在部分场合可以对关系数据库起到很好的补充作用,满足实时的高并发需求。 Redis跟memcached类似&#…...
【送书活动】AI时代,程序员需要焦虑吗?
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄,vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄ÿ…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
