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

news2024/11/18 2:40:21

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、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/660991.html

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

相关文章

记录好项目D8

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是超市管理系统 技术栈&#xff1a;springbootjavamysqlthyemleafshiro …

数据安全--24--数据安全管理之API管理

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/131274853 一、API管理概述 API是指应用程序编程接口&#xff0c;它的存在主要是为了提高系统各组成单元的内聚性&#xff0c;降低组成单元之间的耦合程度&#xff08;相互依赖程度&#xff09;&#…

[元带你学: eMMC协议详解 18] eMMC的后台操作(Background Operations)

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 内容摘要 全文 1800 字&#xff0c; 主要内容 1、后台操作/前台操作区别&#xff1f; 2、如何进行手动后台操作&#xff1f; 3、自动后台启动和停止方法&a…

【Redis基础】

Redis基础 Redis基础Note Redis基础1.初识Redis1.1.认识NoSQL1.1.1.结构化与非结构化1.1.2.关联和非关联1.1.3.查询方式1.1.4.事务1.1.5.总结 1.2.认识Redis1.3.安装Redis1.3.1.依赖库1.3.2.上传安装包并解压1.3.3.启动1.3.4.默认启动1.3.5.指定配置启动1.3.6.开机自启1.3.7.wi…

web安全自学笔记

Web 安全方面的基本知识是有很必要的&#xff0c;未必就要深入理解。本文主要介绍常见的网络攻击类型&#xff0c;不作深入探讨。 正文 网络攻击的形式种类繁多&#xff0c;从简单的网站敏感文件扫描、弱口令暴力破解&#xff0c;到 SQL 注入&#xff0c;再到复杂的网络劫持等…

基于Web的影院信息管理系统设计与实现(论文+源码)_kaic

摘要 随着文化产业的发展&#xff0c;电影行业迎来了发展的黄金期&#xff0c;而且人民生活水平的 不断提高&#xff0c;观众对影院的服务要求也越来越高。传统的服务模式&#xff0c;已经不能满足 观众的需求&#xff0c;随着信息技术的发展&#xff0c;越来越多的影院将信息技…

Qt 中动态加载窗口

在编程中&#xff0c;我经常会遇见要根据用户触发按钮&#xff0c;动态生成窗口的情况。在此有两种方法可以动态生成窗口&#xff1a;一&#xff1a;直接在槽函数中调用窗口类。二&#xff1a;将 **.ui 添加到资源文件&#xff0c;通过 QUiLoader 加载。 现将两种方法介绍如下…

LAMPDISCUZ论坛

目录 1.1 LAMP 1.2 LAMP架构搭建 2. 关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下 APache的源码编译安装涉及到的软件包&#xff1a; 2.2 安装环境依赖包 2.3 配置软件模块 2.4 添加httpd系统服务 2.5 修改httpd 服务配置文件 3.编译安装mysqld 服务 3.1 …

1. CSS的三大特性

CSS有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级 1.1层叠性 相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖(层叠)另一个冲突的样式。层叠性主要解决样式冲突 的问题 层叠性原则: ●样式冲突,遵循的原则是就近原则,哪个样式离结构近,就执行哪个…

记一次Windows 下Microsoft Store应用权限问题

关键字&#xff1a;windows、 microsoft store 画图 mspaint 终端 terminal Windows11 锁屏界面黑屏 起因 情不知所起&#xff0c;额走错片场了。。。 具体原因无法确定&#xff0c;猜测是由于之前磁盘故障后进行修复导致的权限丢失异常 表象 几乎是所有的Microsoft store的…

Java-反射机制(超详解)

Java反射机制概述 前言一、Java反射机制概述1. Java Reflection2. 动态语言 vs 静态语言二、 Class类的理解1. 类的加载过程1.1 初步了解1.2 类的加载过程图解1.3 了解&#xff1a;什么时候会发生类初始化&#xff1f;1.4 类加载器的作用1.5 JVM中不同类型的类的加载器1.6 代码…

SpringMVC系列-1 使用方式和启动流程

背景 SpringMVC作为SSM组件之一&#xff0c;Java开发有必要了解SpringMVC是如何被集成到Spring框架以及整个项目的启动流程。本文以Tomcat作为Servlet容器进行介绍&#xff0c;默认认为读者使用过Tomcat且对Tomcat内部组件有足够的理解。 1.启动流程 当Tomcat被部署到服务器…

node的安装配置

这个是官网下载地址 推荐选择下载LTS版本的下载&#xff0c;不推荐下载最新版的&#xff0c;下载完成之后&#xff0c;在安装过程中更改安装路径默认是c盘的&#xff0c;如果c盘空间大当我没说&#xff0c;接着一路傻瓜式安装默认点击下一步&#xff0c;直到安装完成。 创建文…

chatgpt赋能python:Python怎样设置字体大小

Python 怎样设置字体大小 作为一名Python工程师&#xff0c;我们经常需要在论文或文档中使用Python进行数据分析&#xff0c;数据可视化和数据科学等方面的工作。在这些文档中&#xff0c;字体大小的设置是非常重要的&#xff0c;因为它直接影响阅读体验和文档的可读性。因此&…

幸运九宫格抽奖码九宫格抽奖系统独立版源码php修复版

&#x1f389; 有需要的朋友记得关赞评&#xff0c;文章底部来交流&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 1.本程序在PHP5.3 ~ PHP5.6环境 2.后台访问地址&#xff1a;域名/admin/index.php U:admin P:admin 手动安装 1.导入1776.sql到数据库 …

【文件操作与IO】认识文件

目录 认识文件 狭义上的文件 广义上的文件 树形结构组织和目录 文件路径&#xff08;Path&#xff09; 其他知识 认识文件 狭义上的文件 硬盘上保存的数据&#xff0c;都是“文件”来组织的&#xff0c;本质上都是二进制或是字符组织的数组&#xff0c;被打包成一个文件…

Git学习笔记(上篇)

导航小助手 一、Git初识 1.1 场景 1.2 版本控制器 1.3 注意事项 二、Git安装 2.1 Linux-centos 2.2 Linux-ubuntu 三、Git基本操作 3.1 创建Git本地仓库 3.2 配置Git本地仓库 3.3 认识工作区、暂存区、版本库 3.3.1 添加文件—场景一 3.3.2 添加文件—场景二 3.4…

chatgpt赋能python:Python怎么重新执行代码

Python怎么重新执行代码 如果你正在学习编程&#xff0c;你可能已经意识到Python是一种非常流行的语言。Python是一种如此强大的编程语言&#xff0c;可以应用于许多领域&#xff0c;从数据分析到游戏开发&#xff0c;甚至是Web开发。但是&#xff0c;在编写Python代码时&…

026.【图形结构算法】

1. 图的定义 树形结构用于描述节点和节点之间的层次关系&#xff0c;而图形结构用于描述两个顶点之间是否连通的关系。在计算机科学中&#xff0c;图形结构是最灵活的数据结构之一&#xff0c;很多问题都可以使用图来求解。 无向图是每条边都没有方向的图&#xff0c;同一个边…

chatgpt赋能python:重新安装Python——让你的编程之路更畅通

重新安装Python——让你的编程之路更畅通 Python是一种高级编程语言&#xff0c;广泛应用于软件开发、数据科学、机器学习等领域&#xff0c;因其易学易用、拥有丰富的第三方库和社区支持而备受程序员们的喜爱。但是&#xff0c;有时候你可能会遇到无法解决的Python问题&#…