【实战】 八、用户选择器与项目编辑功能(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十五)

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
    • 四、JWT、用户认证与异步请求
    • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
    • 六、用户体验优化 - 加载中和错误状态处理
    • 七、Hook,路由,与 URL 状态管理
    • 八、用户选择器与项目编辑功能
      • 1~3
      • 4.编辑后刷新-useState的懒初始化与保存函数状态
      • 5.完成编辑后刷新功能


学习内容来源: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 状态管理(下)

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

1~3

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

4.编辑后刷新-useState的懒初始化与保存函数状态

之前的遗留问题现在尝试解决

修改 src\utils\use-async.ts(新增 rerun 方法, 保存上一次 run 的运行状态):

...
export const useAsync = <D>(...) => {...const [rerun, setRerun] = useState(() => {})...// run 来触发异步请求const run = (promise: Promise<D>) => {if (!promise || !promise.then) {throw new Error("请传入 Promise 类型数据");}setRerun(() => run(promise))setState({ ...state, stat: "loading" });return promise.then(...).catch(...);};return {...// rerun 重新运行一遍 run, 使得 state 刷新rerun,...state,};
};

相对直接定义变量,通过 useState 定义的变量在组件刷新时会保持之前的状态,除非重新setState, 而直接定义会重新初始化

src\screens\ProjectList\index.tsx 中尝试调用,使用前先打印一下:

...
export const ProjectList = () => {...const { isLoading, error, data: list, rerun } = useProjects(useDebounce(param));...console.log('rerun', rerun)return (<Container><h1>项目列表</h1>{/* <Button onClick={rerun}>rerun</Button> */}...</Container>);
};
...

…有报错:Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

尝试在 rerun 中,run 执行前面一步打印,编辑src\utils\use-async.ts

...
export const useAsync = <D>(...) => {...const run = (promise: Promise<D>) => {...setRerun(() => {console.log('set rerun')run(promise)})...};...
};

一直不停打印 “set rerun”, 但此时并没有运行 rerun,这样就有理由怀疑在 rerun 赋值时就会直接执行,即 useState 不能直接保存函数

codesandbox 上测试一下:

export default function App() {const [lazyValue, setLazyValue] = React.useState(() => {console.log('i am lazy')})console.log(lazyValue);return (<div className="App"><button onClick={() => setLazyValue(() => { console.log('update lazyValue') })}>setCallback</button><button onClick={lazyValue}>call callback</button></div>);
}

果然,不仅在赋值时,初始化时就直接执行了

看下 useState 的函数签名:

/*** Returns a stateful value, and a function to update it.** @version 16.8.0* @see https://reactjs.org/docs/hooks-reference.html#usestate*/
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

可以注意到 initialState 是一个联合类型, S 是常用的形式,() => S 为啥要单列出来呢?

可以查看官方文档:惰性初始 state | Hook API 索引 – React

通过文档可以得知,这种初始化方式仅执行一次,且用于初始化值需要复杂计算才能得到的情况(昂贵的计算,消耗性能)

既然如此,何不在外面再加一层函数呢?试一下:

export default function App() {const [callback, setCallback] = React.useState(() => () => {console.log('i am callback')})console.log(callback);return (<div className="App"><button onClick={() => setCallback(() => () => { console.log('update callback') })}>setCallback</button><button onClick={callback}>call callback</button></div>);
}

果然在初始化后可以直接调用 callback,在 setCallback 后再调用又是另一个函数了

除了这种方式,还可以使用 useRef

export default function App() {const callbackRef = React.useRef(() => console.log('i am callback'));const callback = callbackRef.current;console.log(callback);return (<div className="App"><button onClick={() => (callbackRef.current = () => console.log('update callback'))}>setCallback</button><button onClick={callback}>call callback</button></div>);
}

https://codesandbox.io/s/blissful-water-230u4?file=/src/App.js

使用 useRef 时需要注意,改变用其定义的值 不会触发组件重新渲染,因此 callback 还是之前的值,必须直接执行 callbackRef.current()

export default function App() {const callbackRef = React.useRef(() => console.log("i am callback"));const callback = callbackRef.current;console.log(callback);return (<div className="App"><buttononClick={() =>(callbackRef.current = () => console.log("update callback"))}>setCallback</button><button onClick={() => callbackRef.current()}>call callback</button></div>);
}

接下来使用第一种方式,外面多加一层函数来处理一下

5.完成编辑后刷新功能

编辑 src\utils\use-async.ts(外面多加一层函数):

...
export const useAsync = <D>(...) => {...const [rerun, setRerun] = useState(() => () => {})...const run = (promise: Promise<D>) => {...setRerun(() => () => run(promise))...};...
};

还是不行。。。通过分析发现执行 rerunrun 中拿到的还是上一次执行的 Promise(上一次调用接口), 因此从上一次执行完的 Promise 拿数据自然还是上次的数据,由此可见需要更新 Promise(重新调用接口)

修改 src\screens\ProjectList\index.tsx(rerun 按钮取消注释):

...
export const ProjectList = () => {...return (<Container><h1>项目列表</h1><Button onClick={rerun}>rerun</Button>...</Container>);
};
...

编辑 src\utils\project.ts(单独抽离 fetchProject,执行第一次作为 run 的第一个参数,预执行包装后作为第二个参数):

...
export const useProjects = (param?: Partial<Project>) => {...const fetchProject = () => client("projects", { data: cleanObject(param || {}) })useEffect(() => {run(fetchProject(), { rerun: fetchProject });// eslint-disable-next-line react-hooks/exhaustive-deps}, [param]);return result;
};
...

编辑 src\utils\use-async.ts(为 run 新增 runConfig):

...
export const useAsync = <D>(...) => {...// run 来触发异步请求const run = (promise: Promise<D>, runConfig?: { rerun: () => Promise<D> }) => {...setRerun(() => () => {if(runConfig?.rerun) {run(runConfig.rerun(), runConfig)}});...};...
};

虽然 定义的 runConfig 是可选参数,但是,若要下一次 rerun 可用,前一次就必须配置好预请求,因此在 setRerun 中,runConfig 是一定要加的,其他地方若是不需要这个功能,可以不加!!!

查看页面,点击按钮执行 rerun,可行了!!!

接下来完善使其编辑后自动 rerun

修改 src\screens\ProjectList\index.tsx(删掉之前测试用的按钮和日志打印,为 List 传入 refreshrerun):

...
export const ProjectList = () => {...return (<Container>...<List refresh={rerun} loading={isLoading} users={users || []} dataSource={list || []} /></Container>);
};
...

修改 src\screens\ProjectList\components\List.tsx(接收传入的传入 refresh,并在starProject的最后执行):

...
interface ListProps extends TableProps<Project> {users: User[];refresh?: () => void;
}// type PropsType = Omit<ListProps, 'users'>
export const List = ({ users, ...props }: ListProps) => {const { mutate } = useEditProject();// 函数式编程 柯里化const starProject = (id: number) => (star: boolean) => mutate({ id, star }).then(props.refresh);return (...);
};

查看页面效果,完美!

下面遗留一些问题:

  • 乐观更新
    • 成功 ? 免loading : 回滚并提示
  • 想调用的方法离触发组件太远怎么办?
    • 状态提升?太复杂的时候不好用
    • 全局状态管理

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

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

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

相关文章

简单学会MyBatis原生API注解

&#x1f600;前言 本篇博文是关于MyBatis原生API&注解的使用&#xff0c;希望能够帮助到你&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您…

VUE使用docxtemplater导出word(带图片) 踩坑 表格循环空格 ,canvas.toDataURL图片失真模糊问题

参考&#xff1a;https://www.codetd.com/article/15219743 安装 // 安装 docxtemplater npm install docxtemplater pizzip --save // 安装 jszip-utils npm install jszip-utils --save // 安装 jszip npm install jszip --save // 安装 FileSaver npm install file-save…

【力扣每日一题】2023.7.29 环形链表

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个链表&#xff0c;让我们判断这个链表是否有环。我们可以直接遍历这个链表&#xff0c;最后能走到链表末尾也就是空指针那就…

VMware虚拟机无法上网的解决办法

&#xff08;1&#xff09;1、在虚拟机右下角的网络适配器上面观察该图标是否是有绿色的灯在闪烁&#xff0c;如果网络适配器是灰色的证明虚拟机的网络没有打开&#xff0c;而是被禁用了&#xff0c;在适配器上点击鼠标右键&#xff0c;打开【设置】&#xff0c;在【已连接】、…

数据结构—链表

链表 前言链表链表的概念及结构链表的分类 无头单向非循环链表的相关实现带头双向循环链表的相关实现顺序表和链表&#xff08;带头双向循环链表&#xff09;的区别 前言 顺序表是存在一些固有的缺陷的&#xff1a; 中间/头部的插入删除&#xff0c;时间复杂度为O(N)&#xf…

windows C++多线程同步<2>-事件

windows C多线程同步&#xff1c;2&#xff1e;-事件 事件对象和关键代码段不同&#xff0c;它是属于内核对象&#xff1b;又分为人工重置事件对象和自动重置事件对象&#xff1b; 同一个线程不允许在不释放事件的情况下多次获取事件&#xff1b; 相关API 白话来讲&#xff1…

认识 springboot 并了解它的创建过程 - 1

前言 本篇介绍什么是SpringBoot, SpringBoot项目如何创建&#xff0c;认识创建SpringBoot项目的目录&#xff0c;了解SpringBoo特点如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流&#xff0c;共同进步&#xff01; 文章目录 前言1.什么是springboot?2.为什么…

Rust之通用集合类型

在Rust语言中包含了一系列被称为集合的数据结构。大部分的数据结构都代表着某个特定的值&#xff0c;但集合却可以包含多个值。与内置的数组与元组类型不同&#xff0c;这些集合将自己持有的数据存储在了堆上。这意味着数据的大小不需要在编译时确定&#xff0c;并且可以随着程…

PKG内容查看工具:Suspicious Package for Mac安装教程

Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用&#xff0c;Suspicious Package Mac版支持查看全部包内全部文件&#xff0c;比如需要运行的脚本&#xff0c;开发者&#xff0c;来源等等。 suspicious package mac使用简单&#xff0c;只需在选择pkg安…

农业中的计算机视觉 2023

物体检测应用于检测田间收割机和果园苹果 一、说明 欢迎来到Voxel51的计算机视觉行业聚焦博客系列的第一期。每个月&#xff0c;我们都将重点介绍不同行业&#xff08;从建筑到气候技术&#xff0c;从零售到机器人等&#xff09;如何使用计算机视觉、机器学习和人工智能来推动…

网络安全-防御需知

目录 网络安全-防御 1.网络安全常识及术语 资产 漏洞 0day 1day 后门 exploit APT 2.什么会出现网络安全问题&#xff1f; 网络环境的开放性 协议栈自身的脆弱性 操作系统自身的漏洞 人为原因 客观原因 硬件原因 缓冲区溢出攻击 缓冲区溢出攻击原理 其他攻击…

网络安全行业相关证书

一&#xff1a;前言 对于考证这个话题&#xff0c;笔者的意见是&#xff1a;“有比没有好&#xff0c;有一定更好&#xff0c;但不一定必须&#xff1b;纸上证明终觉浅&#xff0c;安全还得实力行”。很多人对于各种机构的考证宣传搞得是云里雾里&#xff0c;不知道网络安全行业…

Codeforces 1579G DP / 二分 + bitset

题意 传送门 Codeforces 1579G Minimal Coverage 题解 DP d p [ i 1 ] [ j ] dp[i1][j] dp[i1][j] 代表 0 ⋯ i 0\cdots i 0⋯i 次移动后所在位置与覆盖区域最左侧位置相差 j j j 时&#xff0c;覆盖区域的最小值。枚举左右方向递推即可。总时间复杂度 O ( n ⋅ max ⁡ …

flex盒子 center排布,有滚动条时,拖动滚动条无法完整显示内容

文章目录 问题示例代码解决问题改进后的效果 问题 最近在开发项目的过程中&#xff0c;发现了一个有趣的事情&#xff0c;与flex盒子有关&#xff0c;不知道算不算是一个bug&#xff0c;不过对于开发者来说&#xff0c;确实有些不方便&#xff0c;感兴趣的同学不妨也去试试。 …

设计模式-建造者模式

在前面几篇文章中&#xff0c;已经讲解了单例模式、工厂方法模式、抽象工厂模式&#xff0c;创建型还剩下一个比较重要的模式-建造者模式。在理解该模式之前&#xff0c;我还是希望重申设计模式的初衷&#xff0c;即为解决一些问题而提供的优良方案。学习设计模式遗忘其初衷&am…

关于Spring中的@Configuration中的proxyBeanMethods属性

Configuration的proxyBeanMethods属性 在Configuration注解中&#xff0c;有两个属性&#xff1a; value配置Bean名称proxyBeanMethos&#xff0c;默认是true 这个proxyBeanMethods的默认属性是true。 直接说&#xff1a;当Configuration注解的proxyBeanMeathods属性是true…

VLAN原理(Virtual LAN 虚拟局域网)

VLAN&#xff08;Virtual LAN 虚拟局域网&#xff09; 1、广播/广播域 2、广播的危害&#xff1a;增加网络/终端负担&#xff0c;传播病毒&#xff0c; 3、如何控制广播&#xff1f;&#xff1f; ​ 控制广播隔离广播域 ​ 路由器物理隔离广播 ​ 路由器隔离广播缺点&…

rocketmq 5.13任意时间延迟消息

原理是采用timewhile 实现的&#xff0c;源码分析可以参考 https://blog.csdn.net/sinat_14840559/article/details/129266105 除了useDelayLevel 已经默认改为false private boolean useDelayLevel false;官方示意代码在public class TimerMessageProducer for (int i 0;…

解决在云服务器开放端口号以后telnet还是无法连接的问题

这里用阿里云服务器举例&#xff0c;在安全组开放了对应的TCP端口以后。使用windows的cmd下的telnet命令&#xff0c;还是无法正常连接。 telnet IP地址 端口号解决方法1&#xff1a; 在轻量服务器控制台的防火墙规则中添加放行端口。 阿里云-管理防火墙 如图&#xff0c;开放…

基于java在线个人网站源码设计与实现

摘 要 随着社会及个人社交应用平台的飞速发展&#xff0c;人们的沟通成本逐渐降低&#xff0c;互联网信息的普及也进一步提升了人们对于信息的需求度&#xff0c;通过建立个人网站的方式来展示自己的生活信息同时利用平台结交新的朋友&#xff0c;借助个人网站平台的搭建不仅可…