【实战】十一、看板页面及任务组页面开发(六) —— 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
      • 11&12
      • 13.任务组页面 (下)
      • 14.完成 popover
      • 15.开发完成,部署页面


学习内容来源: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&8

  • 十一、看板页面及任务组页面开发(三)

9&10

  • 十一、看板页面及任务组页面开发(四)

11&12

  • 十一、看板页面及任务组页面开发(五)

13.任务组页面 (下)

新建 src\screens\TaskGroup\components\createTaskGroup.js:

import React, { useEffect } from "react";
import { Button, Drawer, Form, Input, Spin } from "antd";
import { DrawerProps } from "antd/es/drawer";
import styled from "@emotion/styled";
import { ErrorBox } from "components/lib";
import { useAddTaskGroup } from "utils/taskGroup";
import { useTaskGroupsQueryKey } from "screens/TaskGroup/utils";
import { useForm } from "antd/es/form/Form";
import { useProjectIdInUrl } from "screens/ViewBoard/utils";export const CreateTaskGroup = (props: Pick<DrawerProps, "visible"> & { onClose: () => void }
) => {const { mutate: addEpic, isLoading, error } = useAddTaskGroup(useTaskGroupsQueryKey());const [form] = useForm();const projectId = useProjectIdInUrl();const onFinish = async (values: any) => {await addEpic({ ...values, projectId });props.onClose();};useEffect(() => {form.resetFields();}, [form, props.visible]);return (<Drawervisible={props.visible}onClose={props.onClose}forceRender={true}destroyOnClose={true}width={"100%"}><Container>{isLoading ? (<Spin size={"large"} />) : (<><h1>创建任务组</h1><ErrorBox error={error} /><Formform={form}layout={"vertical"}style={{ width: "40rem" }}onFinish={onFinish}><Form.Itemlabel={"名称"}name={"name"}rules={[{ required: true, message: "请输入任务组名" }]}><Input placeholder={"请输入任务组名称"} /></Form.Item><Form.Item style={{ textAlign: "right" }}><Buttonloading={isLoading}type={"primary"}htmlType={"submit"}>提交</Button></Form.Item></Form></>)}</Container></Drawer>);
};const Container = styled.div`height: 80vh;display: flex;flex-direction: column;justify-content: center;align-items: center;
`;

编辑 src\screens\TaskGroup\index.tsx(新增创建任务组功能):

...
import { CreateTaskGroup } from "./components/createTaskGroup";export const TaskGroupIndex = () => {...const [epicCreateOpen, setEpicCreateOpen] = useState(false);...return (<ViewContainer><Row between={true}><h1>{currentProject?.name}任务组</h1><Button onClick={() => setEpicCreateOpen(true)} type={"link"}>创建任务组</Button></Row><Liststyle={{ overflow: "scroll" }}dataSource={taskGroups}itemLayout={"vertical"}renderItem={...}/><CreateTaskGrouponClose={() => setEpicCreateOpen(false)}visible={epicCreateOpen}/></ViewContainer>);
};

查看页面, 尝试新增任务组,功能正常!

注意,若是发现新增或删除后页面数据没有变化,那说明新增或删除时使用的 queryKeyuseQuery 中入参不一致,导致无法正常更新缓存数据

14.完成 popover

功能实现的差不多了,接下来查漏补缺
效果图

从图中可以看到,在项目列表页中,取消或新增收藏项目,页面左上角收藏夹不会自动更新,查看代码并结合控制台发现 ProjectPopover 中没有给 useProjects 传入参数作为 queryKey,因此在缓存中 这两个地方(收藏夹和项目列表并不一致)

编辑 src\screens\ProjectList\components\ProjectPopover.tsx(使用 refetch,在 onOpenChange 中调用):

...export const ProjectPopover = () => {...const { data: projects, refetch } = useProjects();...return (<Popover onOpenChange={() => refetch()} placement="bottom" content={content}>项目</Popover>);
};
...

查看页面,这样功能正常啦!

编辑 src\utils\use-users.ts(改为 react-query 的方式):

import { useHttp } from "utils/http";
import { User } from "types/User";
import { useQuery } from "react-query";export const useUsers = (param?: Partial<User>) => {const client = useHttp();return useQuery<User[]>(["users", param], () =>client("users", { data: param }));
};

新建 src\screens\ProjectList\components\UserPopover.tsx(页面布局有一部分与 ProjectPopover 相同,可以拿过来 src\screens\ProjectList\components\ProjectPopover.tsx):

import React from "react";
import { Divider, List, Popover, Typography } from "antd";
import styled from "@emotion/styled";
import { useUsers } from "utils/use-users";export const UserPopover = () => {const { data: users, refetch } = useUsers();const content = (<ContentContainer><Typography.Text type={"secondary"}>组员列表</Typography.Text><List>{users?.map((user) => (<List.Item key={user.id}><List.Item.Meta title={user.name} /></List.Item>))}</List><Divider /></ContentContainer>);return (<PopoveronVisibleChange={() => refetch()}placement={"bottom"}content={content}><span>组员</span></Popover>);
};const ContentContainer = styled.div`min-width: 30rem;
`;

编辑 src\authenticated-app.tsx(使用 UserPopover 组件)

...
import { UserPopover } from "screens/ProjectList/components/UserPopover";export const AuthenticatedApp = () => {...
};const PageHeader = () => {...return (<Header between={true}><HeaderLeft gap={true}>...<ProjectPopover /><UserPopover /></HeaderLeft><HeaderRight>...</HeaderRight></Header>);
};

查看页面,显示正常!

15.开发完成,部署页面

按视频教程是部署到 github pages

github 上新建一个仓库,仓库名必须为 username.github.io,其中 usernamegithub 用户名。

安装依赖

npm i gh-pages -D
npm i deploy

修改 package.json(配置部署脚本)

{"scripts": {..."predeploy": "npm run build","deploy": "gh-pages -d build -r git@github.com:sindu12jun/sindu12jun.github.io.git -b main"},
}

注意要将 git 链接替换成自己的,并指定分支(main 替代之前 master 作为 github 默认主分支)

安装好后执行:npm run deploy(predeploy 也会自动执行),打包后的文件会自动上传到 github 并生成提交记录

打开部署后的页面,成功!

但是还有个问题,github 如何判断路由是前端路由还是后端路由?

目前点到其他子页面,刷新后 显示404

接下来解决这个问题,参考 spa-github-pages: 使用GitHub Pages 托管单页应用程序

大概步骤如下:

  1. 自定义 404 页面,将当前的路由信息记录下来
  2. 携带路由信息跳转到 index
  3. 进入 index 后检查路由信息,进行还原

新建 public\404.html(配置404页面,解决强制刷新页面时,github pages当做后端路由,从而报错的问题)

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Single Page Apps for GitHub Pages</title><script type="text/javascript">// Single Page Apps for GitHub Pages// MIT License// https://github.com/rafgraph/spa-github-pages// This script takes the current url and converts the path and query// string into just a query string, and then redirects the browser// to the new url with only a query string and hash fragment,// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe// Note: this 404.html file must be at least 512 bytes for it to work// with Internet Explorer (it is currently > 512 bytes)// If you're creating a Project Pages site and NOT using a custom domain,// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).// This way the code will only replace the route part of the path, and not// the real directory in which the app resides, for example:// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe// Otherwise, leave pathSegmentsToKeep as 0.var pathSegmentsToKeep = 0;var l = window.location;l.replace(l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +l.hash);</script>
</head>
<body>
</body>
</html>

编辑 public\index.html(将路由信息完整的带到 index 中还原,从而实现 SPA 的效果,当然,会看到浏览器地址栏中路由的跳转,体验上稍微差了点,不过功能上已经基本无异。)

<!DOCTYPE html>
<html lang="en"><head>...<title>Jira任务管理系统</title><!-- Start Single Page Apps for GitHub Pages --><script type="text/javascript">// Single Page Apps for GitHub Pages// MIT License// https://github.com/rafgraph/spa-github-pages// This script checks to see if a redirect is present in the query string,// converts it back into the correct url and adds it to the// browser's history using window.history.replaceState(...),// which won't cause the browser to attempt to load the new url.// When the single page app is loaded further down in this file,// the correct url will be waiting in the browser's history for// the single page app to route accordingly.(function(l) {if (l.search[1] === '/' ) {var decoded = l.search.slice(1).split('&').map(function(s) {return s.replace(/~and~/g, '&')}).join('?');window.history.replaceState(null, null,l.pathname.slice(0, -1) + decoded + l.hash);}}(window.location))</script><!-- End Single Page Apps for GitHub Pages --></head><body>...</body>
</html>

英文注释译文:该脚本检查查询字符串中是否存在重定向,将其转换回正确的 url,并使用 window.history.replaceState(…) 将其添加到浏览器的历史记录中,这不会导致浏览器尝试加载新url。当单页应用程序在这个文件中被进一步加载时,正确的 url 将在浏览器的历史记录中等待单页应用程序相应地路由。


解决方案原地址:https://github.com/rafgraph/spa-github-pages

  • 为方便访问,博主在gitee上 fork 了一份 https://gitee.com/OliverDaDa_admin/spa-github-pages

这样部署就完成啦!


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

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

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

相关文章

电商平台api接口拼多多根据关键词获取商品列表示例

拼多多根据关键词获取商品接口的意义&#xff1b; 实现商品搜索&#xff1a;通过关键词搜索商品API接口&#xff0c;电商平台可以为消费者提供一个简单、快捷的商品搜索功能。用户只需输入关键词&#xff0c;就可以得到与该关键词相关的商品列表。 提供便捷的商品搜索服务&…

关于IT学习随感

随感 之前一阵子的学习&#xff0c;感觉轻飘飘的 一直是被灌输的学习一些相关技术 不知到原因不会用 仅限于看得懂&#xff0c;现在回想确实 不仅如此 以前也是如此 一直都没有思索过&#xff0c;最近机缘巧合了解到学习it的三部what how why 自己且不讲why层 how层都没怎么主…

ATF(TF-A)安全通告 TFV-10 (CVE-2022-47630)

安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-10 (CVE-2022-47630) 二、CVE-2022-47630 2.1 Bug 1&#xff1a;证书校验不足 2.2 Bug 2&#xff1a;auth_nvctr()中缺少边界检查 2.3 可利用性分析 2.3.1 上游TF-A代码 2.3.…

IIS搭建本地电脑服务器:通过内网穿透技术实现公网访问的步骤指南

1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xff0c;比如网页运行的Apache和Nginx、数据库软件MySQL和MSSQL之类&#xff0c;为方便用户使用&#xff0c;还出现了XAMPP、PHPStudy、宝塔面板等等一系列集成服务&#xff0c;都是为了方便我们…

应用程序管理工具

应用程序管理是 DevOps 的重要组成部分。它可以定义为在所有阶段监控和管理软件应用程序的可用性、运行状况、性能和功能的过程&#xff0c;包括规划、设计、构建、测试、部署、维护和更新。这意味着应用程序从概念到停止都受到监控。 应用程序管理的重要性 管理应用程序可确…

uni-app中使用iconfont彩色图标

uni-app中使用iconfont彩色图标 大家好&#xff0c;今天我们来学习一下uni-app中使用iconfont彩色图标&#xff0c;好好看&#xff0c;好好学&#xff0c;超详细的 第一步 首先&#xff0c;从iconfont官网&#xff08;iconfont-阿里巴巴矢量图标库&#xff09;选择自己需要的图…

vue3中的pinia

pinia官网 main.ts中引入 import { createApp } from vue import { createPinia } from pinia import ./style.css const store createPinia() import App from ./App.vuecreateApp(App).use(store).mount(#app)创建store文件夹&#xff0c;创建index.ts import {defineSto…

ChatGPT⼊门到精通(6):ChatGPT 提问设计

前⾔ 学会提问就是为了让AI给出⾼质量的答案。 你所学到的技能⼀切为了⽣成⾼质量的答案。 本教程适合&#xff1a;普通ChatGPT的⽤户、专业prompt⼯程师 你将收获&#xff1a;prompt 技巧的全⾯指导 、prompt⼯程师必备技能、prompt技术⼯程⾼质量答 案完全指南 提⽰词 Prom…

数据库导出工具

之前根据数据库升级需求&#xff0c;需要导出旧版本数据&#xff08;sqlserver 6.5&#xff09;&#xff0c;利用c# winfrom写了一个小工具&#xff0c;导出数据。 →→→→→多了不说&#xff0c;少了不唠。进入正题→→→→ 连接数据库&#xff1a;输入数据库信息 连接成功…

合并对象在 Typescript 中的实现与应用

合并对象在 Typescript 中的实现与应用 文章目录 合并对象在 Typescript 中的实现与应用一、简介二、实现1、函数实现2、参数说明3、返回值 三、使用示例四、实际应用场景五、拓展&#xff1a;使用 lodash-es 的 assign 函数进行对象合并1、简介2、安装与导入3、基础用法4、注意…

K8S:K8S自动化运维容器Docker集群

文章目录 一.k8s概述1.k8s是什么2.为什么要用K8S3.作用及功能4.k8s容器集群管理系统 二.K8S的特性1.弹性伸缩2.自我修复3.服务发现和复制均衡4.自动发布和回滚5.集中化配置管理和秘钥管理6.存储编排7.任务批量处理运行 三.K8S的集群架构四、K8S的核心组件1、Master组件&#xf…

Linux ptrace系统调用

文章目录 一、ptrace 简介二、ptrace 参数request2.1 PTRACE_TRACEME2.2 PTRACE_PEEKTEXT, PTRACE_PEEKDATA2.3 PTRACE_PEEKUSER2.4 PTRACE_POKETEXT, PTRACE_POKEDATA2.5 PTRACE_POKEUSER2.6 PTRACE_GETREGS, PTRACE_GETFPREGS2.7 PTRACE_GETREGSET2.8 PTRACE_SETREGS, PTRACE…

前端文件、图片直传OOS、分片上传、el-upload上传(vue+elementUI)

前言&#xff1a;基于天翼云的面相对象存储(Object-Oriented Storage&#xff0c;OOS),实现小文件的直接上传&#xff0c;大文件的分片上传。 开发文档地址&#xff1a;网址 上传之前的相关操作&#xff1a;注册账户&#xff0c;创建 AccessKeyId 和 AccessSecretKey之后&…

mac环境使用pkgbuild命令打pkg包的几个小细节

mac环境使用pkgbuild命令打pkg包的几个小细节 最近&#xff0c;研发提出要使用jenkins来自动生成mac环境下的pkg包&#xff0c;研究了一下&#xff0c;可以使用pkgbuild来打包。但是有几个小细节需要注意一下&#xff1a; 1 如果有pre-install和post-install脚本&#xff0c;…

批量根据excel数据绘制饼状图

要使用Python批量根据Excel数据绘制饼状图&#xff0c;可以使用pandas和matplotlib库来实现。以下是一个基本的代码示例&#xff1a; import pandas as pd import matplotlib.pyplot as plt # 读取Excel文件 data pd.read_excel(data.xlsx) # 提取需要用于绘制饼状图的数据列…

问题:UITableViewCellContentView:height == 44 约束冲突

使用Masonry自动布局&#xff0c;高度自定义&#xff0c;出现如标题所示约束冲突 <NSLayoutConstraint:UITableViewCellContentView:.height 44> 解决方案: 在设置底部约束的时候设置 高优先级priorityHigh() 即可 [self.auditBtn mas_makeConstraints:^(MASConstra…

36种水果和蔬菜识别(pytorch框架,深度卷积网络模型,可以实现照片连续识别和视频识别)

1.效果视频&#xff1a;36种水果和蔬菜识别&#xff08;pytorch框架&#xff0c;深度卷积网络模型&#xff0c;可以实现照片连续识别和视频识别&#xff09;_哔哩哔哩_bilibili 2.项目文件夹 第一个文件夹&#xff08;data&#xff09;&#xff1a; 装载的是原始图像 第二个文…

移动端通讯录相关代码

vue3更新版本 附件地址 代码在附件里噜 太多了不好贴

【MYSQL】排序时 如何将0排到最后,并让其他值按正序展示?

背景&#xff1a;展示排名时需要1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;这样展示但是有些没有排名得数据字段默认值时0&#xff0c;这时直接用ASC就会出现问题 实现效果 实现方式&#xff1a;使用MySQL的ORDER BY语句来实现。以下是一个示例的SQL查…

Python编程——深入了解不可变的元组

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;Python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注。 目录 一、元组是什么 二、元组的定义 1、相同类型组成元组…