【实战】十一、看板页面及任务组页面开发(三) —— 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.看板和任务删除功能


学习内容来源: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
prettier2.8.4
json-server0.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.编辑任务功能

接下来新建编辑任务的组件:

先准备好调用编辑任务接口和获取任务详情的 Hook,编辑 src\utils\task.ts

...
import { useAddConfig, useEditConfig } from "./use-optimistic-options";export const useEditTask = (queryKey: QueryKey) => {const client = useHttp();return useMutation((params: Partial<Task>) =>client(`tasks/${params.id}`, {method: "PATCH",data: params,}),useEditConfig(queryKey));
};export const useTask = (id?: number) => {const client = useHttp();return useQuery<Task>(["task", id], () => client(`tasks/${id}`), {enabled: Boolean(id),});
};

编辑 src\screens\ViewBoard\utils.ts(新增 useTasksModal):

...
// import { useDebounce } from "utils";
import { useTask } from "utils/task";
...export const useTasksSearchParams = () => {const [param] = useUrlQueryParam(["name","typeId","processorId","tagId",]);const projectId = useProjectIdInUrl();// const debouncedName = useDebounce(param.name)return useMemo(() => ({projectId,typeId: Number(param.typeId) || undefined,processorId: Number(param.processorId) || undefined,tagId: Number(param.tagId) || undefined,// name: debouncedName,name: param.name,}),// [projectId, param, debouncedName][projectId, param]);
};...export const useTasksModal = () => {const [{ editingTaskId }, setEditingTaskId] = useUrlQueryParam(['editingTaskId'])const { data: editingTask, isLoading } = useTask(Number(editingTaskId))const startEdit = useCallback((id: number) => {setEditingTaskId({editingTaskId: id})}, [setEditingTaskId])const close = useCallback(() => {setEditingTaskId({editingTaskId: ''})}, [setEditingTaskId])return {editingTaskId,editingTask,startEdit,close,isLoading}
}

视频中使用 useDebounce 使得完全停止输入后才开始搜索,避免输入过程中频繁搜索造成系统资源浪费,且影响用户体验,博主这样更改后中文输入法无法正常使用。。。后续再解决

新建组件:src\screens\ViewBoard\components\taskModal.tsx

import { useForm } from "antd/lib/form/Form"
import { useTasksModal, useTasksQueryKey } from "../utils"
import { useEditTask } from "utils/task"
import { useEffect } from "react"
import { Form, Input, Modal } from "antd"
import { UserSelect } from "components/user-select"
import { TaskTypeSelect } from "components/task-type-select"const layout = {labelCol: {span: 8},wrapperCol: {span: 16}
}export const TaskModal = () => {const [form] = useForm()const { editingTaskId, editingTask, close } = useTasksModal()const { mutateAsync: editTask, isLoading: editLoading } = useEditTask(useTasksQueryKey())const onCancel = () => {close()form.resetFields()}const onOk = async () => {await editTask({...editingTask, ...form.getFieldsValue()})close()}useEffect(() => {form.setFieldsValue(editingTask)}, [form, editingTask])return <ModalforceRender={true}onCancel={onCancel}onOk={onOk}okText={"确认"}cancelText={"取消"}confirmLoading={editLoading}title={"编辑任务"}open={!!editingTaskId}><Form {...layout} initialValues={editingTask} form={form}><Form.Itemlabel={"任务名"}name={"name"}rules={[{ required: true, message: "请输入任务名" }]}><Input /></Form.Item><Form.Item label={"经办人"} name={"processorId"}><UserSelect defaultOptionName={"经办人"} /></Form.Item><Form.Item label={"类型"} name={"typeId"}><TaskTypeSelect /></Form.Item></Form></Modal>
}

注意:与 Drawer 一样,在Modal 组件中使用通过 useForm() 提取的 form 绑定的 Form 时,需要添加 forceRender 属性,否则在页面打开时绑定不到会有报错,参见:【实战】React 实战项目常见报错 —— Instance created by ‘useForm’ is not connected to any Form element. Forget…

编辑:src\screens\ViewBoard\index.tsx(引入 TaskModal):

...
import { TaskModal } from "./components/taskModal";export const ViewBoard = () => {...return (<ViewContainer>...<TaskModal/></ViewContainer>);
};
...

编辑:src\screens\ViewBoard\components\ViewboardCloumn.tsx(引入 useTasksModal 使得点击 任务卡片 可以打开 TaskModal 进行编辑):

...
import { useTasksModal, useTasksSearchParams } from "../utils";
...export const ViewboardColumn = ({ viewboard }: { viewboard: Viewboard }) => {...const { startEdit } = useTasksModal()return (<Container>...<TasksContainer>{tasks?.map((task) => (<Card onClick={() => startEdit(task.id)} style={{ marginBottom: "0.5rem", cursor: 'pointer' }} key={task.id}>...</Card>))}...</TasksContainer></Container>);
};
...

查看功能和效果,点击 任务卡片 后 TaskModal 出现,编辑并确认后即可看到修改后的任务(用了乐观更新,完全无感):
在这里插入图片描述

8.看板和任务删除功能

接下来先实现一个小功能,搜索结果中关键字高亮

新建 src\screens\ViewBoard\components\mark.tsx

export const Mark = ({name, keyword}: {name: string, keyword: string}) => {if(!keyword) {return <>{name}</>}const arr = name.split(keyword)return <>{arr.map((str, index) => <span key={index}>{str}{index === arr.length -1 ? null : <span style={{ color: '#257AFD' }}>{keyword}</span>}</span>)}</>
}

编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx(引入 Task 并将 TaskCard 单独提取出来):

...
import { Task } from "types/Task";
import { Mark } from "./mark";...const TaskCard = ({task}: {task: Task}) => {const { startEdit } = useTasksModal();const { name: keyword } = useTasksSearchParams()return <CardonClick={() => startEdit(task.id)}style={{ marginBottom: "0.5rem", cursor: "pointer" }}key={task.id}><p><Mark keyword={keyword} name={task.name}/></p><TaskTypeIcon id={task.id} /></Card>
}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) => <TaskCard task={task}/>)}<CreateTask kanbanId={viewboard.id} /></TasksContainer></Container>);
};
...

查看效果:

在这里插入图片描述

下面开始开发删除功能

编辑 src\utils\viewboard.ts(创建并导出 useDeleteViewBoard):

...
export const useDeleteViewBoard = (queryKey: QueryKey) => {const client = useHttp();return useMutation((id?: number) =>client(`kanbans/${id}`, {method: "DELETE",}),useDeleteConfig(queryKey));
};

编辑 src\screens\ViewBoard\components\ViewboardCloumn.tsx

...
import { Button, Card, Dropdown, MenuProps, Modal, Row } from "antd";
import { useDeleteViewBoard } from "utils/viewboard";...export const ViewboardColumn = ({ viewboard }: { viewboard: Viewboard }) => {const { data: allTasks } = useTasks(useTasksSearchParams());const tasks = allTasks?.filter((task) => task.kanbanId === viewboard.id);return (<Container><Row><h3>{viewboard.name}</h3><More viewboard={viewboard}/></Row><TasksContainer>{tasks?.map((task) => <TaskCard task={task}/>)}<CreateTask kanbanId={viewboard.id} /></TasksContainer></Container>);
};const More = ({ viewboard }: { viewboard: Viewboard }) => {const {mutateAsync: deleteViewBoard} = useDeleteViewBoard(useViewBoardQueryKey())const startDelete = () => {Modal.confirm({okText: '确定',cancelText: '取消',title: '确定删除看板吗?',onOk() {deleteViewBoard(viewboard.id)}})}const items: MenuProps["items"] = [{key: 1,label: "删除",onClick: startDelete,},];return <Dropdown menu={{ items }}><Button type="link" onClick={(e) => e.preventDefault()}>...</Button></Dropdown>
}
...

测试一下删除看板,功能正常

下面是删除任务功能

编辑 src\utils\task.ts(创建并导出 useDeleteTask):

...
export const useDeleteTask = (queryKey: QueryKey) => {const client = useHttp();return useMutation((id?: number) =>client(`tasks/${id}`, {method: "DELETE",}),useDeleteConfig(queryKey));
};

编辑 src\screens\ViewBoard\components\taskModal.tsx

...
import { useDeleteTask, useEditTask } from "utils/task";export const TaskModal = () => {...const { mutateAsync: deleteTask } = useDeleteTask(useTasksQueryKey());...const startDelete = () => {close();Modal.confirm({okText: '确定',cancelText: '取消',title: '确定删除看板吗?',onOk() {deleteTask(Number(editingTaskId));}})}return (<Modal {...}><Form {...}>...</Form><div style={{ textAlign: 'right' }}><Button style={{fontSize: '14px'}} size="small" onClick={startDelete}>删除</Button></div></Modal>);
};

测试一下删除任务,功能正常


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/100562.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Spring Boot的智慧团支部建设网站的设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于Spring Boot的智慧团支部建设网站的设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java sp…

stm32的命令规则

stm32型号的说明&#xff1a;以STM32F103RBT6这个型号的芯片为例&#xff0c;该型号的组成为7个部分&#xff0c;其命名规则如下&#xff1a;

生成式AI和大语言模型 Generative AI LLMs

在“使用大型语言模型(LLMs)的生成性AI”中&#xff0c;您将学习生成性AI的基本工作原理&#xff0c;以及如何在实际应用中部署它。 通过参加这门课程&#xff0c;您将学会&#xff1a; 深入了解生成性AI&#xff0c;描述基于LLM的典型生成性AI生命周期中的关键步骤&#xff…

OpenAI首席执行官谈马斯克退出的影响;关于 Stable Diffusion 扩展功能的内容总结

&#x1f989; AI新闻 &#x1f680; OpenAI首席执行官谈马斯克退出的影响 摘要&#xff1a;OpenAI首席执行官萨姆奥特曼在接受采访时表示&#xff0c;埃隆马斯克退出OpenAI给公司带来了巨大震惊&#xff0c;导致奥特曼不得不重新调整生活和时间安排以确保足够资金。OpenAI是…

hive中get_json_object函数不支持解析json中文key

问题 今天在 Hive 中 get_json_object 函数解析 json 串的时候&#xff0c;发现函数不支持解析 json 中文 key。 例如&#xff1a; select get_json_object({ "姓名":"张三" , "年龄":"18" }, $.姓名);我们希望的结果是得到姓名对应…

七夕节日表白:七大网页风格与其适用人群

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

SVM详解

公式太多了&#xff0c;就用图片用笔记呈现&#xff0c;SVM虽然算法本质一目了然&#xff0c;但其中用到的数学推导还是挺多的&#xff0c;其中拉格朗日约束关于α>0这块证明我看了很长时间&#xff0c;到底是因为悟性不够。对偶问题也是&#xff0c;用了一个简单的例子才明…

文心一言 VS 讯飞星火 VS chatgpt (79)-- 算法导论7.4 4题

四、请用go语言&#xff0c;证明:RANDOMIZED-QUICKSORT 期望运行时间是 Ω(nlgn)。 文心一言&#xff1a; 首先&#xff0c;为了证明RANDOMIZED-QUICKSORT的期望运行时间是Ω(nlg n)&#xff0c;我们需要证明在最坏的情况下&#xff0c;该算法的运行时间是O(nlg n)。然后&…

1.0的星火2.0必将燎原——图文声影PPT全测试

一、前言 大家好&#xff0c;勇哥又来分享AI模型了&#xff0c;前几天讯飞发布的星火大模型2.0迅速的进入了我们圈子里&#xff0c;为了有更多更好的模型分享给大家&#xff0c;分享星火大模型2.0是必须做的&#xff0c;我做一个传递着&#xff0c;希望大家也星火相传啊。 我…

解决执行 spark.sql 时版本不兼容的一种方式

场景描述 hive 数据表的导入导出功能部分代码如下所示&#xff0c;使用 assemble 将 Java 程序和 spark 相关依赖一起打成 jar 包&#xff0c;最后 spark-submit 提交 jar 到集群执行。 public class SparkHiveApplication {public static void main(String[] args){long sta…

操作系统——shell编程

文章目录 shell入门什么是 Shell&#xff1f;Shell 编程的 Hello World Shell 变量Shell 编程中的变量介绍Shell 字符串入门Shell 字符串常见操作Shell 数组 Shell 基本运算符算数运算符关系运算符逻辑运算符布尔运算符字符串运算符文件相关运算符 shell流程控制if 条件语句for…

市面上那里有稳定L2股票行情数据接口?

随着市场的发展和技术的进步&#xff0c;level2股票行情数据接口已经成为股票交易软件的标准配置之一。虽然这些券商软件的功能在很大程度上相似&#xff0c;但它们仍然有自己的特点和优势。 例如&#xff1a;通过股票交易所以其专业的研究报告和丰富的信息服务而受到广泛关注&…

Shell编程基础02

0目录 1.case语法 2.grep 3.sed 4.awk 5.linux安装mysql 1.case语法 创建一个txt文档 执行 查询用户名 case 用法 写一个计算器脚本 加入函数 补充查看进程命名 2.find grep命令 Find 查询当前目录下 以sh结尾的文件 Grep 查询义开头的 或者加入正则表达…

Java --- 二维数组

一、二维数组的定义 public class TwoArrayTest {public static void main(String[] args) {//二维数组声明与初始化//方式1&#xff1a;静态初始化int[][] arr new int[][]{{1,2,3},{1,2,3},{1,2,3}};//方式2&#xff1a;动态初始化int[][] arr2 new int[3][3];arr2[0][1] …

开学季有哪些电容笔值得买吗?便宜的电容笔推荐

开学有哪些电容笔值得入手呢&#xff1f;这款名为Apple Pencil的平替电容笔&#xff0c;在压感功能方面这一项上&#xff0c;与Apple Pencil相比的主要区别就是&#xff0c;平替电容笔仅仅只拥有倾斜的压感功能&#xff0c;但用来书写和做笔记还是绰绰有余的&#xff0c;价格便…

C++系列-引用

引用 引用的基本使用引用的起源引用的语法引用的本质引用的注意事项引用和指针 引用作为函数参数引用作为函数的返回值常量引用其它用返回值方式调用函数&#xff08;case 1&#xff09;用函数的返回值初始化引用的方式调用函数&#xff08;case 2&#xff09;用返回引用的方式…

探工业互联网的下一站!腾讯云助力智造升级

引言 数字化浪潮正深刻影响着传统工业形态。作为第四次工业革命的重要基石&#xff0c;工业互联网凭借其独特的价值快速崛起&#xff0c;引领和推动着产业变革方向。面对数字化时代给产业带来的机遇与挑战&#xff0c;如何推动工业互联网的规模化落地&#xff0c;加速数字经济…

【Axure模板】APP帮助中心原型,在线客服意见反馈模块高保真原型

作品概况 页面数量&#xff1a;共 10 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;原型设计模板 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 该模板作品为APP帮助与客服的通用模块&#xff0c;…

MySQL分页查询-性能优化

MySQL分页查询优化 一、背景二、原因三、解决四、原理探究 https://blog.csdn.net/hollis_chuang/article/details/130570281 一、背景 业务背景&#xff1a;给C端10万级别的用户&#xff0c;同时发送活动消息&#xff0c;活动消息分为6类。数据背景&#xff1a;mysql表有百万…

拒绝摆烂!C语言练习打卡第五天

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;每日一练 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、选择题 &#x1f4dd;1.第一题 &#x1f4dd;2.第二题 &#x1f4d…