【实战】 二、React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
      • 1.新建文件
      • 2.状态提升
      • 3.新建utils
      • 4.Custom Hook


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

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


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

  • 【实战】 一、项目起航:项目初始化与配置 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(一)

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

1.新建文件

  • 新建文件:src\screens\ProjectList\index.jsx
import { SearchPanel } from "./components/SearchPanel"
import { List } from "./components/List"export const ProjectListScreen = () => {return <div><SearchPanel/><List/></div>
}
  • 新建文件:src\screens\ProjectList\components\List.jsx
export const List = () => {return <table></table>
}
  • 新建文件:src\screens\ProjectList\components\SearchPanel.jsx
import { useEffect, useState } from "react"export const SearchPanel = () => {const [param, setParam] = useState({name: '',personId: ''})const [users, setUsers] = useState([])const [list, setList] = useState([])useEffect(() => {fetch('').then(async res => {if (res.ok) {setList(await res.json())}})}, [param])return <form><div>{/* setParam(Object.assign({}, param, { name: evt.target.value })) */}<input type="text" value={param.name} onChange={evt => setParam({...param,name: evt.target.value})}/><select value={param.personId} onChange={evt => setParam({...param,personId: evt.target.value})}><option value="">负责人</option>{users.map(user => (<option key={user.id} value={user.id}>{user.name}</option>))}</select></div></form>
}
  • 相对原视频,这里为组件命名使用的是:大驼峰法(Upper Camel Case)
  • 相对原视频,目录结构和变量名都可以按自己习惯来的哦!
  • 编码过程很重要,但文字不好体现。。。
  • vscode 在 JS 文件中不会自动补全 HTML标签可参考:【小技巧】vscode 在 JS 文件中补全 HTML标签

2.状态提升

由于 listparam 涉及两个不同组件,因此需要将这两个 state 提升到他们共同的父组件中,子组件通过解构 props 使用:

  • listList 消费;
  • list 根据 param 获取;
  • paramSearchPanel 消费;

按照数据库范式思维,projectusers 各自单独一张表、而 list 只是关联查询的中间产物,hard 模式中通过 project 只能得到 users 的主键,即 personId,需要根据 personId 再去获取 personName,因此 users 也需要做状态提升


为了 DRY 原则,将接口调用URL中的 http://host:port 提取到 项目全局环境变量 中:

  • .env
REACT_APP_API_URL=http://online.com
  • .env.development
REACT_APP_API_URL=http://localhost:3001

webpack 环境变量识别规则的理解:

  • 执行 npm start 时,webpack 读取 .env.development 中的环境变量;
  • 执行 npm run build 时,webpack 读取 .env 中的环境变量;

3.新建utils

常用工具方法统一放到 utils/index.js

  • 由于在fetch传参过程中,多个可传参数单只传一个,那么空参需要过滤(过滤过程中考虑到 0 是有效参数,因此特殊处理):
export const isFalsy = val => val === 0 ? false : !val// 在函数里,不可用直接赋值的方式改变传入的引用类型变量
export const cleanObject = obj => {const res = { ...obj }Object.keys(res).forEach(key => {const val = res[key]if (isFalsy(val)) {delete res[key]}})return res
}
  • Falsy - MDN Web 文档术语表:Web 相关术语的定义 | MDN
  • 在url后拼参时,参数较多会显得繁琐,因此引入 qs
npm i qs
  • qs - npm

经过前面两步,状态提升并使用 cleanObjectqs 处理参数后,源码如下:

  • src\screens\ProjectList\index.jsx
import { SearchPanel } from "./components/SearchPanel";
import { List } from "./components/List";
import { useEffect, useState } from "react";
import { cleanObject } from "utils";
import * as qs from 'qs'const apiUrl = process.env.REACT_APP_API_URL;export const ProjectListScreen = () => {const [users, setUsers] = useState([]);const [param, setParam] = useState({name: "",personId: "",});const [list, setList] = useState([]);useEffect(() => {fetch(// name=${param.name}&personId=${param.personId}`${apiUrl}/projects?${qs.stringify(cleanObject(param))}`).then(async (res) => {if (res.ok) {setList(await res.json());}});}, [param]);useEffect(() => {fetch(`${apiUrl}/users`).then(async (res) => {if (res.ok) {setUsers(await res.json());}});}, []);return (<div><SearchPanel users={users} param={param} setParam={setParam} /><List users={users} list={list} /></div>);
};
  • src\screens\ProjectList\components\List.jsx
export const List = ({ users, list }) => {return (<table><thead><tr><th>名称</th><th>负责人</th></tr></thead><tbody>{list.map((project) => (<tr key={project.id}><td>{project.name}</td>{/* undefined.name */}<td>{users.find((user) => user.id === project.personId)?.name ||"未知"}</td></tr>))}</tbody></table>);
};
  • src\screens\ProjectList\components\SearchPanel.jsx
export const SearchPanel = ({ users, param, setParam }) => {return (<form><div>{/* setParam(Object.assign({}, param, { name: evt.target.value })) */}<inputtype="text"value={param.name}onChange={(evt) =>setParam({...param,name: evt.target.value,})}/><selectvalue={param.personId}onChange={(evt) =>setParam({...param,personId: evt.target.value,})}><option value="">负责人</option>{users.map((user) => (<option key={user.id} value={user.id}>{user.name}</option>))}</select></div></form>);
};
  • src\App.tsx
import "./App.css";
import { ProjectListScreen } from "screens/ProjectList";function App() {return (<div className="App"><ProjectListScreen /></div>);
}export default App;

现在效果:可以通过项目名和人名筛选(全匹配)
效果图

4.Custom Hook

Custom Hook 可是代码复用利器

  • useMount:生命周期模拟 —— componentDidMount
export const useMount = cbk => useEffect(() => cbk(), [])

正常情况下 useEffect 只执行一次,但是 react@v18 严格模式下 useEffect 默认执行两遍,具体详见:【已解决】react@v18 严格模式下 useEffect 默认执行两遍

  • useDebounce:防抖
/*** @param { 值 } val * @param { 延时:默认 1000 } delay * @returns 在某段时间内多次变动后最终拿到的值(delay 延迟的是存储在队列中的上一次变化)*/
export const useDebounce = (val, delay = 1000) => {const [tempVal, setTempVal] = useState(val)useEffect(() => {// 每次在 val 变化后,设置一个定时器const timeout = setTimeout(() => setTempVal(val), delay)// 每次在上一个 useEffect 处理完以后再运行(useEffect 的天然功能即是在运行结束的 return 函数中清除上一个(同一) useEffect)return () => clearTimeout(timeout)}, [val, delay])return tempVal
}// 日常案例,对比理解// const debounce = (func, delay) => {
//   let timeout;
//   return () => {
//     if (timeout) {
//       clearTimeout(timeout);
//     }
//     timeout = setTimeout(function () {
//       func()
//     }, delay)
//   }
// }// const log = debounce(() => console.log('call'), 5000)
// log()
// log()
// log()
//   ...5s
// 执行!// debounce 原理讲解:
// 0s ---------> 1s ---------> 2s --------> ...
//     这三个函数是同步操作,它们一定是在 0~1s 这个时间段内瞬间完成的;
//     log()#1 // timeout#1
//     log()#2 // 发现 timeout#1!取消之,然后设置timeout#2
//     log()#3 // 发现 timeout#2! 取消之,然后设置timeout#3
//             // 所以,log()#3 结束后,就只有最后一个 —— timeout#3 保留

拓展学习:【笔记】深度理解并 js 手写不同场景下的防抖函数

  • 使用了 Custom Hook 后的 src\screens\ProjectList\index.js(lastParam 定义在紧挨 param 后)
  ...// 对 param 进行防抖处理const lastParam = useDebounce(param)const [list, setList] = useState([]);useEffect(() => {fetch(// name=${param.name}&personId=${param.personId}`${apiUrl}/projects?${qs.stringify(cleanObject(lastParam))}`).then(async (res) => {if (res.ok) {setList(await res.json());}});}, [lastParam]);useMount(() => {fetch(`${apiUrl}/users`).then(async (res) => {if (res.ok) {setUsers(await res.json());}});});...

这样便可 1s 内再次输入不会触发对 projectsfetch 请求

拓展学习:

  • 【笔记】Custom Hook

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

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

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

相关文章

浅浅总结一下雅思听力技巧

1. 地图题 读题步骤要明确 &#xff08;1&#xff09;看图&#xff0c;要看看题目中是否有东南西北的标志&#xff0c;如果有的话&#xff0c;那么大概率题目中就会用到。同时也标记好左右的标志&#xff0c;防止考试的时候太紧张分不清。 弄清楚个元素的相对位置&#xff0…

Python web框架开发 - WSGI协议

目录 浏览器请求动态页面过程 多进程web服务端代码 - 面向过程 封装对象分析 增加识别动态资源请求的功能 为什么需要 WSGI协议 WSGI协议的介绍 定义WSGI接口 编写framwork支持WSGI协议&#xff0c;实现浏览器显示 hello world 本次开发的完整代码如下&#xff1a; 浏…

社区活动 | OpenVINO™ DevCon 中国系列工作坊第二期 | 使用 OpenVINO™ 加速生成式 AI...

生成式 AI 领域一直在快速发展&#xff0c;许多潜在应用随之而来&#xff0c;这些应用可以从根本上改变人机交互与协作的未来。这一最新进展的一个例子是 GPT 模型的发布&#xff0c;它具有解决复杂问题的能力&#xff0c;比如通过医学和法律考试这种类似于人类的能力。然而&am…

MySql脚本 asc 排序字段空值条目靠后的写法

场景&#xff1a; mysql中如果使用正序 asc 排序&#xff0c;那么默认是把排序字段值为空的条目数据&#xff0c;优先排到前面&#xff0c;这明显不符合需求&#xff0c;解决如下 一、重现问题 -- 按排序号-正序 select shop_id,sort_num,update_time from t_shop_trend_conte…

SpringMvc中文件上传

文章目录 1.导入文件上传所需要的jar包 2. 配置文件解析器 3.写一个前端页面 4.写后台程序 1.导入文件上传所需要的jar包 <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.…

华为FIT痩AP旁挂式隧道组网实验(一)

拓扑图 实验设备型号ACAC6005S1S5700S2S3700APAP2050DNAP4AP2050DNAR1AR200 没有配置好之前,是没有这个AP范围圈的 配置流程 接入交换机创建VLAN,配置对应端口的链路类型,放行vlan,开启端口隔离 # 与AP连接的接口(0/0/2) [S2]vlan batch 100 101 [S2]int e0/0/2 [S2-Ethern…

finalshell使用方法,前端vue更新服务器项目

首先我们看看finalshell的整体 上面是xshell一样&#xff0c;可以输命令 上面是WinSCP一样&#xff0c;可以直接拖文件&#xff0c;下载&#xff0c;上传&#xff0c;可视化视图 1.下载服务器文件 服务器文件通过Jenkins打包上去的&#xff0c;首先我们把文件下载到本地 点击…

ranger配置hive出錯:Unable to connect repository with given config for hive

ranger配置hive出錯&#xff1a;Unable to connect repository with given config for hive 我一開始我以為是我重啟了ranger-admin導致ranger有點問題&#xff0c;後面排查之後發現是我之前把hiveserver2關閉了&#xff0c;所以只需要重新開啟hiveserver2即可

Mysql主从同步失败排查思路及解决办法

1、查看同步信息 登录进从数据库后查询同步状态 show slave status \G 2、查看同步失败出现的日志 Coordinator stopped because there were error(s) in the worker(s). The most r ecent failure being: Worker 1 failed executing transaction 55b49392-fdcd-11ec-83b2-…

已烧写过的镜像重新烧镜像教程

本教程是已经烧录过镜像的SD卡&#xff0c;无法被电脑识别盘符导致无法重新烧录镜像的教程。一般是win7系统无法识别烧录过的Ubuntu系统盘符。win10可以使用SDformat软件格式化。 1.确定读卡器是否识别到SD卡。 点击计算机右键选择“管理”&#xff0c;选择磁盘管理&#xff0…

【AI底层逻辑】——篇章4:大数据处理与挖掘

目录 引入 一、大数据概述 二、数据处理的流程&方法 1、数据收集——“从无到有” 2、数据加工——“从有到能用” 3、数据分析 三、大数据改变了什么 往期精彩&#xff1a; 引入 AI的表现依赖大数据。曾经一段时间&#xff0c;对于图像识别的准确率只能达到60%~70…

接口测试和功能测试的区别

目录 前言&#xff1a; 一、测试目的不同 二、测试内容不同 三、测试重点不同 总结 前言&#xff1a; 接口测试和功能测试都是软件测试中非常重要的测试类型&#xff0c;它们都是验证软件产品的正确性、完备性、正确性和可靠性。但这两者之间有着一些区别。 一、测试目的…