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

news2024/11/19 1:44:02

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、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 })) */}
        <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>
  );
};
  • 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.coloradmin.cn/o/706700.html

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

相关文章

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第十二天 12/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

服务运营|MS 论文解读:MIR 增强的Benders 分解用于具有到达率不确定性的多类服务系统人员调度问题

推文作者&#xff1a;王畅&#xff0c;蔡君洋&#xff0c;Guo 编者按 劳动力调度是服务系统运营的一个重要应用场景&#xff0c;当系统中的服务集和客户集异构且客户到达率不确定时&#xff0c;劳动力调度问题会变得特别复杂。本文因此引入了一个两阶段的随机整数规划模型&…

【静态单元格 Objective-C语言】

一、预习QQ动态 1.我们今天,是三、到四个任务,第一个任务,就是这个,预习QQ动态 OK,看到这个效果了吗 这是咱们第一个任务,第一个任务,是实现这么一个效果, 一看这个,就肯定是个啥,UITableView吧, 是不是肯定是一个TableView啊 又能滚动,又能显示一行一行的数据…

Qt QGraphicsScene、QGraphicsView类实现仪表盘

Qt QGraphicsScene、QGraphicsView类实现仪表盘 【1】UI界面设计【2】效果【3】QGraphicsScene简介【4】QGraphicsEllipseItem简介【5】QGraphicsPolygonItem简介【6】QGraphicsLineItem简介【7】QGraphicsView简介【8】仪表源码头文件源码 【1】UI界面设计 【2】效果 【3】QGr…

OpenStack(T版)——对象存储(Swift)服务介绍与安装

文章目录 OpenStack(T版)——对象存储(Swift)服务介绍与安装安装和配置(controller)准备安装和配置Swift对象存储服务组件创建账户 Ring创建容器 Ring创建对象 RingSwift 存储系统的主配置文件修改文件属主 安装和配置(compute)准备配置Swift对象存储服务组件 验证(1)加载环境变…

《程序员的AI书_从代码开始》第二章 手工实现神经网络

文章目录 2.1 感知器2.1.1 从神经元到感知器2.1.2 实现简单的感知器 2.1 感知器 2.1.1 从神经元到感知器 2.1.2 实现简单的感知器 class Perception(object):def __init__(self,eta0.01,iterations10):self.lr etaself.iterations iterationsself.w 0.0self.bias 0.0sel…

Red Hat宣布限制访问源代码,尚有两种方式可获取

日前Red Hat宣布限制访问Red Hat Enterprise Linux(RHEL)源代码&#xff0c;而此举也将影响AlmaLinux 、Rocky Linux 等下游发行版。 随即Rocky Linux发布了名为《Keeping Open Source Open》的博文。文中是这样说的&#xff1a;    Red Hat的**ERP**服务条款 (TOS) 和最终…

dotnet-dump/trace

一、dotnet-dump dotnet tool install -g dotnet-dump ps -ef | grep dotnet export PATH$PATH:$HOME/.dotnet/tools dotnet-dump collect --process-id 31555 #等待最后出输出&#xff1a;Writing full to /root/core_20230701_095230 #分析/root/core_20230701_095230文件 d…

【doxygen】支持 markdown 目录

文章目录 创建章节和子章节配置 doxygen Doxygen 不直接支持 Markdown 的 [toc] 语法。但是&#xff0c;你可以通过以下步骤手动创建目录&#xff1a; 创建章节和子章节 在 Markdown 文件中&#xff0c;使用标题标记&#xff08; # &#xff09;来创建章节和子章节。 Note:…

D2L运行环境问题+anaconda 多环境

环境问题 背景&#xff1a; 换了电脑&#xff0c;当前的电脑原先就装着Python3.9.0 直接pip install d2l & torch can work but when i start the jupyter, it use the anaconda SDK which is 3.6.4 so i need to install the d2l& torch under anaconda python sdk b…

第二章:Fast R-CNN网络详解

(目标检测篇&#xff09;系列文章目录 第一章:R-CNN网络详解 第二章:Fast R-CNN网络详解 第三章:Faster R-CNN网络详解 第四章:YOLO v1网络详解 第五章:YOLO v2网络详解 第六章:YOLO v3网络详解 文章目录 系列文章目录技术干货集锦前言一、摘要二、正文分析 1.引入库2.读…

10个最好的免费PDF阅读器清单分享

PDF&#xff08;便携式文档格式&#xff09;已成为数字文档分发和共享的事实上的标准。要高效地查看、注释和编辑 PDF 文件&#xff0c;可靠的 PDF 阅读器至关重要。在这篇综合文章中&#xff0c;我们将探讨当今可用的前 10 款 PDF 阅读器&#xff0c;评估它们的功能、可用性和…

网工内推 | 云平台运维专场,IE认证优先

01 中科创达 招聘岗位&#xff1a;云平台运维 职责描述&#xff1a; 1、驻场在客户单位处办公&#xff0c;负责H3C品牌的网络、安全、存储、云平台、服务器等软硬件设备的运行监控与管理&#xff0c;包括日常巡检、日常维护等工作&#xff1b; 2、技术支持&#xff0c;为客户…

各牌浏览器设置地址栏显示完整URL

有时候&#xff0c;我们在浏览器的地址栏输入URL后&#xff0c;需要查看完整的URL路径&#xff0c;比如想看到是http协议还是https协议。 目前大多数浏览器都直接将协议头隐藏&#xff0c;需要复制出地址&#xff0c;或者点击地址栏才能看到&#xff0c;比较麻烦。 浏览器支持通…

碳排放预测模型 | Python实现基于LR线性回归和MPR多项式回归的碳排放预测模型

文章目录 效果一览文章概述研究内容源码设计参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于LR线性回归和MPR多项式回归的碳排放预测模型 研究内容 预测碳排放 查看汽车模型的数据集并预测其碳排放量。 在这里,使用线性回归和多项式回归进行预测,以找出哪个模型更…

【Python爬虫】Python爬虫三大基础模块(urllib BS4 Selenium)

【Python爬虫】Python爬虫三大基础模块&#xff08;urllib & BS4 & Selenium&#xff09; 文章目录 【Python爬虫】Python爬虫三大基础模块&#xff08;urllib & BS4 & Selenium&#xff09;一、Python爬虫的基本知识1、网络爬虫是什么&#xff1f;1&#xff0…

phpstudy nginx的解析漏洞分析

然后加入/.php 成功执行phpinfo(); 漏洞分析 这是我的生产环境,根据漏洞特性&#xff0c;大概率就是fastcgi的问题。 查看该站点相关的nginx的配置文件 可见 这里的正则处理,将文件传递给fastcgi处理&#xff0c;$fastcgi_script_name会被设置为1.png/.php&#xff0c;然后构…

kafka入门,Leader Partition 负载平衡(十五)

Leader Partition自动平衡 正常请款下&#xff0c;kafka本身会自动把Leader Partition均匀分散在个个机器上&#xff0c;来保证每台机器的读写吞吐量都是均匀的&#xff0c;但是如果某些broker宕机&#xff0c;会导致Leader partition过于集中在其他少部分几台broker上&#xf…

记录 Linux centos 安装tomact遇到的问题

如果在安装时 觉得自己什么都安装好了&#xff0c;什么也设置好了&#xff0c;包括阿里云的安全组&#xff0c;但是依旧不能进行访问Tomact的主页&#xff0c;你可以查看一下 catalina.out这个文件&#xff0c;出现以下错误这表示 tomact和Java本版有冲突所以一直无法访问&…

局域网内https证书mkcert安装

打开终端&#xff0c;输入如下命令下载mkcert&#xff1a; wget "https://dl.filippo.io/mkcert/latest?forlinux/amd64" -O mkcert && chmod x mkcert安装 mkcert需要依赖certutil&#xff0c;所以安装前需要先安装依赖包 CentOS/RHEL/Fedora/Alma/Rock…