【实战】 二、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 })) */}
        <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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ClickHouse主键索引最佳实践

在本文中&#xff0c;我们将深入研究ClickHouse索引。我们将对此进行详细说明和讨论&#xff1a; ClickHouse的索引与传统的关系数据库有何不同ClickHouse是怎样构建和使用主键稀疏索引的ClickHouse索引的最佳实践 您可以选择在自己的机器上执行本文给出的所有Clickhouse SQL…

SQlite数据库

SQlite数据库 1.SQLite简介 轻量化&#xff0c;易用的嵌入式数据库&#xff0c;用于设备端的数据管理&#xff0c;可以理解成单点的数据库。传统服务器型数据库用于管理多端设备&#xff0c;更加复杂 SQLite是一个无服务器的数据库&#xff0c;是自包含的。这也称为嵌入式数…

2020年国赛高教杯数学建模C题中小微企业的信贷决策解题全过程文档及程序

2020年国赛高教杯数学建模 C题 中小微企业的信贷决策 原题再现 在实际中&#xff0c;由于中小微企业规模相对较小&#xff0c;也缺少抵押资产&#xff0c;因此银行通常是依据信贷政策、企业的交易票据信息和上下游企业的影响力&#xff0c;向实力强、供求关系稳定的企业提供贷…

Win10电脑开机PIN码怎么取消?

有的用户稀里糊涂的设置了PIN码之后&#xff0c;在开机时发现多了个PIN码&#xff0c;但又不知道电脑PIN码是什么意思&#xff0c;也不清楚开机PIN码怎么取消。您可以通过阅读以下内容&#xff0c;以了解什么是PIN以及如何取消PIN码。 PIN码是一种快捷登录密码方式&#xff0c;…

lesson 12 Zigbee绑定通信

目录 Zigbee绑定通信 通信原理 实验过程 实现步骤 实验现象 实验分析 Zigbee绑定通信 通信原理 1、Zigbee一共有五种通信方式&#xff1a;单播、广播、组播、MAC、广播 2、绑定是Zigbee的一种基本通信方式&#xff0c;具体绑定通信又分为三种模式&#xff0c;模式大同…

java 计算网段范围 分析网段包含关系

目录 一、网段范围 二、思路说明 三、代码 1、将一个ip转为数字 2、转换子网掩码&#xff08;255.255.255.0 转为 24&#xff09; 3、根据 ip 与 掩码 计算最大值和最小值 4、测试 5、完整代码 四、难点讲解 1、转换子网掩码&#xff0c; 例&#xff1a;255.255.25…

数据总线学习

为啥要数据总线 使用服务化方式发布&#xff0c;业务端和中间件完全解耦合。一处生产&#xff0c;处处消费设计理念。提供用户可定制的托管化通用消费方案&#xff08;如同步mysql到缓存&#xff0c;同步mysql到es&#xff0c;消费mysql到大数据等托管服务&#xff09; 特性 …

RabbitMQ系列(18)--RabbitMQ基于插件实现延迟队列

1、前往RabbitMQ官网下载往RabbitMQ添加延迟消息的插件 RabbitMQ官网下载插件的网址&#xff1a;https://www.rabbitmq.com/community-plugins.html 2、下载rabbitmq_delayer_message_exchange插件&#xff08;注&#xff1a;RabbitMQ是什么版本的&#xff0c;下载的插件就得是…

【UE5 Cesium】12-Cesium for Unreal 去除左下角的icon

问题 在视口左下角的icon如何去除&#xff1f; 解决方法 打开“CesiumCreditSystemBP” 将“Credit Widget Class”一项中的“ScreenCredit”替换为“ScreenCreditWidget” 编译之后icon就不显示了。

2023年5月PETS5(WSK)考试经验分享

由于本人明年打算出国联培的缘故&#xff0c;CSC国家留学基金委需要申请人的语言成绩达到一定的要求 英语&#xff08;PETS5&#xff09;&#xff1a;笔试总分55分&#xff08;含&#xff09;以上&#xff0c;其中听力部分18分&#xff08;含&#xff09;以上&#xff0c;口试…

2023最新AI创作系统/ChatGPT商业运营版网站程序源码+支持GPT4+支持ai绘画(MJ)+实时语音识别输入+免费更新版本

2023最新AI创作系统/ChatGPT商业运营版网站程序源码支持ai绘画支持GPT4.0实时语音识别输入文章资讯发布功能用户会员套餐免费更新版本 一、AI创作系统二、系统介绍三、系统程序下载四、安装教程五、主要功能展示六、更新日志 一、AI创作系统 1、提问&#xff1a;程序已经支持G…

“生鲜蔬”APP的设计与实现

1.引言 在这个科技与网络齐头并进的时代&#xff0c;外卖服务正在飞速发展&#xff0c;人们对外卖APP系统功能需求越来越多&#xff0c;开发APP的人员对自己的要求也要越来越高&#xff0c;要从所做APP外卖系统所实现的功能和用户的需求来对系统进行设计&#xff0c;还需要与当…

2023年船舶、海洋与海事工程国际会议(NAOME 2023) | Ei Scopus双检索

会议简介 Brief Introduction 2023年船舶、海洋与海事工程国际会议(NAOME 2023) 会议时间&#xff1a;2023年10月20日-22日 召开地点&#xff1a;中国镇江 大会官网&#xff1a;NAOME 2023-2023 International Conference on Naval Architecture and Ocean & Marine Engine…

腾讯云对象存储联合DataBend云数仓打通数据湖和数据仓库

随着数字化进程不断深入&#xff0c;数据呈大规模、多样性的爆发式增长。为满足更多样、更复杂的业务数据处理分析的诉求&#xff0c;湖仓一体应运而生。在Gartner发布的《Hype Cycle for Data Management 2021》中&#xff0c;湖仓一体&#xff08;Lake house&#xff09;首次…

ModaHub魔搭社区:基于阿里云 ACK 搭建开源向量数据库 Milvus

目录 一、准备资源 二、集群创建&#xff1a; 本集群基于Terway网络构建 二、连接刚刚创建的ACK集群 三、部署Milvus数据库 四、优化Milvus配置 简介&#xff1a; 生成式 AI&#xff08;Generative AI&#xff09;引爆了向量数据库&#xff08;Vector Database&#xff0…

【链表OJ】删除链表中重复的结点

⭐️ 往期链表相关OJ &#x1f4ab;链接1&#xff1a;链表分割 &#x1f4ab;链接2&#xff1a;链表中倒数第k个结点(快慢指针问题) &#x1f4ab;链接3&#xff1a;leetcode 876.链表的中间结点(快慢指针问题) &#x1f4ab;链接4&#xff1a;leetcode 206.反转链表 &#x1…

【数据结构与算法】内排序算法比较(C\C++)

实践要求 1. 问题描述 各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶&#xff0c;或大概执行时间&#xff0c;试通过随机的数据比较各算法的关键字比较次数和关键字移动次数&#xff0c;以取得直观感受。 2. 基本要求 对以下10种常用的内部排序算法进行比较…

【mysql实践】如何查看阿里云RDS的MySQL库中的binlog日志

背景&#xff1a; 工作中我们为了查看MySQL中数据修改的历史记录时&#xff0c;会通过查看binlog日志。但由于binlog日志是二进制文件&#xff0c;需要解析之后&#xff0c;才能用文本查看工具打开。这次笔者使用flink进行实时统计时就多次遇到了这个问题。经常看笔者最近博客…

redhat6安装mysql8.0.33

1、下载mysql 官网地址&#xff1a;https://downloads.mysql.com/archives/community/ 下载步骤&#xff1a; 过滤操作系统版本 下载后&#xff0c;上传到服务器Downloads目录 2、安装mysql8 解压压缩包 tar -xvf mysql-8.0.31-1.el9.x86_64.rpm-bundle.tar [rootrhel64 …

山海鲸Cesium:帮你用更简单的方式升级视效

CesiumJS作为绝大多数人都在用的开源地球可视化引擎&#xff0c;视觉效果并不拔尖&#xff0c;这让很多giser都想着有一天升级一下视效&#xff0c;从众多平庸的项目中脱颖而出。然而&#xff0c;对于一些使用Cesium的项目来说&#xff0c;要想达到Cesium for unreal的视觉效果…