【实战】 六、用户体验优化 - 加载中和错误状态处理(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(八)

news2024/12/26 23:26:15

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
    • 四、JWT、用户认证与异步请求
    • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
    • 六、用户体验优化 - 加载中和错误状态处理
      • 1.给页面添加 Loading 和 Error 状态,增加页面友好性
      • 2.用高级 Hook-useAsync 统一处理 Loading 和 Error 状态


学习内容来源: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 应用:实现项目列表

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

三、TS 应用:JS神助攻 - 强类型

  • 【实战】三、 TS 应用:JS神助攻 - 强类型 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(三)

四、JWT、用户认证与异步请求

  • 【实战】四、 JWT、用户认证与异步请求(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

  • 【实战】四、 JWT、用户认证与异步请求(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(五)

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式

  • 【实战】 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(六)

  • 【实战】 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(七)

六、用户体验优化 - 加载中和错误状态处理

1.给页面添加 Loading 和 Error 状态,增加页面友好性

修改 src\screens\ProjectList\index.tsx(新增 loading 状态 和 请求错误提示)(部分未修改内容省略):

...
import { Typography } from "antd";

export const ProjectList = () => {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<null | Error>(null);

  ...

  useEffect(() => {
    setIsLoading(true)
    // React Hook "useHttp" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
    client("projects", { data: cleanObject(lastParam) }).then(setList)
      .catch(error => {
        setList([])
        setError(error)
      })
      .finally(() => setIsLoading(false));
    // React Hook useEffect has a missing dependency: 'client'. Either include it or remove the dependency array.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lastParam]);

  ...

  return (
    <Container>
      <h1>项目列表</h1>
      <SearchPanel users={users} param={param} setParam={setParam} />
      {error ? <Typography.Text type="danger">{error.message}</Typography.Text> : null}
      <List loading={isLoading} users={users} dataSource={list} />
    </Container>
  );
};

...

修改 src\screens\ProjectList\components\List.tsxListProps 继承 TableProps, Table 的属性(透传))(部分未修改内容省略):

import { Table, TableProps } from "antd";
...

interface ListProps extends TableProps<Project> {
  users: User[];
}

// type PropsType = Omit<ListProps, 'users'>
export const List = ({ users, ...props }: ListProps) => {
  return (
    <Table
      pagination={false}
      columns={...}
      { ...props }
    ></Table>
  );
};

为方便后续在组件外再次配置 Table 的属性(透传),直接让 ListProps 继承 TableProps, TableProps 单独抽出到 props

配置请求最短时间(如下图),即可清楚看到 loading 效果
在这里插入图片描述
配置请求失败比例为百分百即可看到错误提示:
在这里插入图片描述

2.用高级 Hook-useAsync 统一处理 Loading 和 Error 状态

新建 src\utils\use-async.ts (统一对 异步状态请求数据 的管理):

import { useState } from "react";

interface State<D> {
  error: Error | null;
  data: D | null;
  stat: 'ready' | 'loading' | 'error' | 'success'
}

const defaultInitialState: State<null> = {
  stat: 'ready',
  data: null,
  error: null
}

export const useAsync = <D>(initialState?: State<D>) => {
  const [state, setState] = useState<State<D>>({
    ...defaultInitialState,
    ...initialState
  })

  const setData = (data: D) => setState({
    data,
    stat: 'success',
    error: null
  })

  const setError = (error: Error) => setState({
    error,
    stat: 'error',
    data: null
  })

  // run 来触发异步请求
  const run = (promise: Promise<D>) => {
    if(!promise || !promise.then) {
      throw new Error('请传入 Promise 类型数据')
    }
    setState({...state, stat: 'loading'})
    return promise.then(data => {
      setData(data)
      return data
    }).catch(error => {
      setError(error)
      return error
    })
  }

  return {
    isReady: state.stat === 'ready',
    isLoading: state.stat === 'loading',
    isError: state.stat === 'error',
    isSuccess: state.stat === 'success',
    run,
    setData,
    setError,
    ...state
  }
}

修改 src\screens\ProjectList\components\List.tsx (将 Project 导出,以便后续引用)(部分未修改内容省略):

...
export interface Project {...}
...

修改 src\screens\ProjectList\index.tsx (部分未修改内容省略):

  • 删去之前 loadingerror 相关内容;
  • 删去 client 异步请求 then 及后续操作;
  • 使用 useAsync 统一处理 异步状态请求数据
...
import { List, Project } from "./components/List";
...
import { useAsync } from "utils/use-async";

export const ProjectList = () => {
  const [users, setUsers] = useState([]);
  const [param, setParam] = useState({
    name: "",
    personId: "",
  });

  // 对 param 进行防抖处理
  const lastParam = useDebounce(param);
  const client = useHttp();
  const { run, isLoading, error, data: list } = useAsync<Project[]>();

  useEffect(() => {
    run(client("projects", { data: cleanObject(lastParam) }))
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lastParam]);

  useMount(() => client("users").then(setUsers));

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

新建 src\utils\project.ts (单独处理 Project 数据的异步请求):

import { cleanObject } from "utils";
import { useHttp } from "./http";
import { useAsync } from "./use-async";
import { useEffect } from "react";
import { Project } from "screens/ProjectList/components/List";

export const useProjects = (param?: Partial<Project>) => {
  const client = useHttp();
  const { run, ...result } = useAsync<Project[]>();

  useEffect(() => {
    run(client("projects", { data: cleanObject(param || {}) }))
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [param]);

  return result
}

新建 src\utils\use-users.ts (单独处理 User 数据的异步请求):

import { cleanObject } from "utils";
import { useHttp } from "./http";
import { useAsync } from "./use-async";
import { useEffect } from "react";
import { User } from "screens/ProjectList/components/SearchPanel";

export const useUsers = (param?: Partial<User>) => {
  const client = useHttp();
  const { run, ...result } = useAsync<User[]>();

  useEffect(() => {
    run(client("users", { data: cleanObject(param || {}) }))
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [param]);

  return result
}

再次修改 src\screens\ProjectList\index.tsx (部分未修改内容省略):

  • ProjectUser 数据获取分别单独抽离
import { SearchPanel } from "./components/SearchPanel";
import { List } from "./components/List";
import { useState } from "react";
import { useDebounce } from "utils";
import styled from "@emotion/styled";
import { Typography } from "antd";
import { useProjects } from "utils/project";
import { useUsers } from "utils/use-users";

export const ProjectList = () => {
  const [param, setParam] = useState({
    name: "",
    personId: "",
  });

  // 对 param 进行防抖处理后接入请求
  const { isLoading, error, data: list } = useProjects(useDebounce(param));
  const { data: users } = useUsers();

  return (
    <Container>
      <h1>项目列表</h1>
      <SearchPanel users={users || []} param={param} setParam={setParam} />
      {error ? (
        <Typography.Text type="danger">{error.message}</Typography.Text>
      ) : null}
      <List loading={isLoading} users={users || []} dataSource={list || []} />
    </Container>
  );
};
...

测试功能:一切正常!


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

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

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

相关文章

web接口开发与测试——你了解多少

目录 前言 Django快速开发之投票系统 编写接口文档 系统接口测试 总结&#xff1a; 前言 最近一直在学习和整理web开发与接口测试的相关资料。接口测试本身毫无任何难度&#xff0c;甚至有很多工具和类库来帮助我们进行接口测试。大多测试人员很难深入了解web接口测试的原…

自然语言生成:解决低频Tokens退化整个Tokens模型问题

Rare Tokens Degenerate All Tokens:Improving Neural Text Generation via Adaptive Gradient Gating for Rare Token Embeddings 摘要 最近的研究发现&#xff0c;大规模神经语言模型学到的Token embeddings&#xff08;令牌嵌入&#xff09;具有退化的各向异性和狭锥形状。…

MobileNet

MobileNet 文章目录 MobileNet单词重要不重要 摘要MobileNetV1Motivation深度可分离卷积逐通道卷积&#xff08;Depthwise Convolution&#xff09;逐点卷积&#xff08;Pointwise Convolution&#xff09;参数对比计算量对比 V2Inverted ResidualsLinear BottlnecksModel Arch…

使用docker进行upload-labs靶场的安装搭建

由于我是在kali上搭建&#xff0c;默认没有docker服务&#xff0c;这里就从按照docker开始讲解 一、docker按装 1、Linux内核版本查看 #如果你是kali可直接从第四步开始 #安装docker要求内核版本kerner>3.10 #为此&#xff0c;先检查当前Linux系统的内核版本 uname -a2…

分析Spring Boot项目启动慢原因之 《一》打印bean耗时

分析Spring Boot项目启动原因之 一 打印bean耗时 由于项目比较久远&#xff0c;积累的代码犹如屎山&#xff0c;然后项目启动也特别慢&#xff0c;然后产生想要优化一下启动速度&#xff0c;故写了一个Bean启动时的耗时分析&#xff1a;package com.tekrally.assetManagement.u…

【Linux】Haproxy搭建Web群集

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Haproxy搭建Web群集 一、Haproxy概述1.常见的Web集群调度器2.Haproxy应用分析3.Haproxy常用调度算法4.Haproxy的主要特性5.LVS、Nginx、Haproxy的区别 二、Haproxy搭建Web群集…

【算法题解】49. 二叉树的序列化与反序列化

这是一道 困难 题 https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/ 文章目录 题目深度优先搜索&#xff08;前序遍历&#xff09;Java 代码实现Go 代码实现复杂度分析 广度优先搜索&#xff08;层序遍历&#xff09;Java 代码实现Go 代码实现复杂度分析 …

容器的本质是什么

广义上来说&#xff0c;容器技术是动态的容器、静态的镜像和远端的仓库这三者的组合。 容器&#xff0c;就是一个特殊的隔离环境&#xff0c;它能够让进程只看到这个环境里的有限信息&#xff0c;不能对外界环境施加影响。 对于 Linux 操作系统来说&#xff0c;一个不受任何限…

【区块链】常见共识机制汇总

文章目录 公有链PoW - Proof of WorkPoS - Proof of StakeDPoS - Delegate Proof of StakePoA - Proof of Activity - 行动证明PoB - Proof of Burn 联盟链PaxosMulti-PaxosPBFTDDBFTIDBFTHotstuffZyzzyvaPoETCasperOurobrosPoSVMG-DPoSRPCAAlgorandTendermint 私有链 公有链 P…

用python实现扫雷游戏

前言 本人最近在学习python语言&#xff0c;发现python是一门很有意思的语音。python有大量的库&#xff0c;大量的函数&#xff0c;可以实现非常多的功能。尤其是在可视化方面&#xff0c;可以画图&#xff0c;可以弹出窗口。于是我就想着看能不能用python编写一个扫雷游戏。…

5. 创建声卡

代码位置 sound/soc/generic/simple-card.c static int asoc_simple_card_probe(struct platform_device *pdev) {...ret devm_snd_soc_register_card(dev, card);... } asoc_simple_card_probe -> devm_snd_soc_register_card -> snd_soc_register_card int snd_soc_r…

MS1825 SDK 移植指南

1. 概述 MS1825 SDK 支持以下 Macrosilicon 芯片&#xff0c;按照芯片功能组合的不同&#xff0c; SDK 中相关 API 和类型的定义有所 不同&#xff0c;请在该文档 API 和类型说明时特别关注&#xff1a; MS1825 SDK 的作用是帮助用户建立基于 MS1825 的视频输入输…

力扣 17. 电话号码的字母组合

题目来源&#xff1a;https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/ C题解&#xff1a; 递归法。 确定回溯函数参数&#xff1a;首先需要一个字符串s来收集叶子节点的结果&#xff0c;一个字符串数组result保存起来&#xff0c;定义为全局…

78-基于stm32单片机电压电流检测LCD1602显示(程序+原理图+元件清单全套资料)...

资料编号&#xff1a;078 功能介绍&#xff1a;采用stm32单片机作为主控CPU&#xff0c;采用精密电阻分压将高电压分压后接入STM32单片机ADC接口&#xff0c;采用ADC可以采集出当前的电压值&#xff0c;通过功率电阻来测量电路中的电流&#xff0c;通过串联电路电流相同的原理&…

正则表达式 教程与简介 | 一看就懂!!!(一)

目录 一、正则表达式 - 教程 二、 正则表达式的模式 &#xff08;一&#xff09;字面值字符 &#xff08;二&#xff09;特殊字符 &#xff08;三&#xff09;字符类 &#xff08;四&#xff09;元字符 &#xff08;五&#xff09;量词 &#xff08;六&#xff09;边界…

如何进行小红书笔记关键词布局,热词分析!

坐拥2.6亿活跃用户&#xff0c;小红书已经成为品牌宣推、种草的重要平台之一。那么品牌进入平台&#xff0c;如何进行小红书笔记关键词布局&#xff0c;热词分析&#xff01; 一、 如何确定关键词 想要做好小红书关键词布局&#xff0c;首先要明确如何确定关键词。 1、当我们要…

shell判断程序是否运行,守护进程

一、需求 服务部署在linux上&#xff0c;要求服务器上的服务可以一直保持正常运行 二、问题 在linux上部署的微服务&#xff0c;不知道什么原因过一段时间就自己停掉了&#xff0c;无法启动。 三、解决办法 添加angle守护进程&#xff0c;通过定时执行脚本来判断程序是否运行…

为什么对ChatGPT、ChatGLM这样的大语言模型说“你是某某领域专家”,它的回答会有效得多?(一)...

“ 太长不看总结版&#xff1a;LLM大模型的本质在于计算某个词汇后面应该跟着哪些词汇的概率。当问题给定了特定的限定范围后&#xff0c;它能够找到一条相对明确的计算路径&#xff0c;从一系列概率分布中挑选出所需的答案。否则&#xff0c;它会根据最常见且最高概率的组合方…

浑元太极马老师和小薇-UMLChina建模知识竞赛第4赛季第7轮[更新]

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 第7轮一直无人得分&#xff0c;再次更换题目。 因有的题目之前已经出过&#xff0c;本轮需要最先答对全…

聊天室(二)__ unipush 推送如何实现?

你想搞个自己的聊天室 app 吗&#xff1f;好多前端同学会好奇聊天室app的推送是怎么搞的&#xff1f;今天就以前端同学使用最多的 uniapp 开发的 app 推送为例&#xff0c;悄悄告诉大家推送是如何实现的&#xff01; 项目技术栈&#xff1a; 项目基于 vue3 的 uniapp 推送基于…