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

这篇具有很好参考价值的文章主要介绍了【实战】 六、用户体验优化 - 加载中和错误状态处理(中) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(九)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


学习内容来源: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
prettier 2.8.4
json-server 0.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~2

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

3.登录注册页面 Loading 和 Error 状态处理,与 Event Loop 详解

列表页的 异步状态 弄完,接下来是登录注册页了

修改 src\unauthenticated-app\index.tsx(新增 error 状态处理,将 error j监听操作 交给 登录注册页):

...
import { Card, Button, Divider, Typography } from "antd";
...

export const UnauthenticatedApp = () => {
  ...
  const [error, setError] = useState<Error | null>(null);
  return (
    <Container>
      ...
      <ShadowCard>
        <Title>{isRegister ? "请注册" : "请登录"}</Title>
        { error ? <Typography.Text type="danger">{error.message}</Typography.Text> : null }
        {isRegister ? <Register onError={setError}/> : <Login onError={setError}/>}
        <Divider />
        ...
      </ShadowCard>
    </Container>
  );
};
...

修改 src\unauthenticated-app\login.tsx(传入 onError 并在异步操作后 catch 中使用):

...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const handleSubmit = (values: { username: string; password: string }) => {
    login(values).catch(e => onError(e))
  };
  ...
};
...

同理修改 src\unauthenticated-app\register.tsx

...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const handleSubmit = (values: { username: string; password: string }) => {
    register(values).catch(e => onError(e))
  };
  ...
};
...

使用非预设用户名密码检验:没反应。。。但是控制台打印出了刚输入的用户名和密码。。。

通过登录的调用链可以找到 导致这个问题的原因:src\auth-provider.ts

  • !res.ok 时,返回了 Promise.reject(data) ,而 data 是请求入参,这显然不是预想的效果(注册同理),修改这部分为 Promise.reject(await res.json())

修改后再次检验,成了!

Promise.catch 固然好用,但接下来换个思路,使用 try..catch 并引出 Event Loop

先修改 src\unauthenticated-app\login.tsx 试试水:

...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const handleSubmit = (values: { username: string; password: string }) => {
    try {
      // login(values).catch(e => onError(e))
      login(values);
    } catch(e: Error | any) {
      onError(e)
    }
  };
  ...
};
...

控制台输出正常,但是界面没有效果。。。

问题出在 login 是异步操作,程序中会优先执行同步操作,然后才会异步操作,所以 onError 优先执行,并没有拿到后端返回的报错信息

再次修改 src\unauthenticated-app\login.tsx (使用 async await 处理异步操作):

...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const handleSubmit = async (values: { username: string; password: string }) => {
    try {
      // login(values).catch(e => onError(e))
      await login(values);
    } catch(e: Error | any) {
      onError(e)
    }
  };
  ...
};
...

这样便正常啦!

接下来给注册页新增确认密码功能

修改 src\unauthenticated-app\register.tsx (新增确认密码的 Form.Item 和 相关处理逻辑):

...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
  const { register, user } = useAuth();
  const handleSubmit = ({ cpassword, ...values }: { username: string, password: string, cpassword: string }) => {
    if (cpassword === values.password) {
      register(values).catch(e => onError(e));
    } else {
      onError(new Error('请确认两次的输入密码相同'))
      return
    }
  };
  return (
    <Form onFinish={handleSubmit}>
      <Form.Item
        name="username"
        rules={[{ required: true, message: "请输入用户名" }]}
      >
        <Input placeholder="用户名" type="text" id="username" />
      </Form.Item>
      <Form.Item
        name="password"
        rules={[{ required: true, message: "请输入密码" }]}
      >
        <Input placeholder="密码" type="password" id="password" />
      </Form.Item>
      <Form.Item
        name="cpassword"
        rules={[{ required: true, message: "请确认密码" }]}
      >
        <Input placeholder="确认密码" type="password" id="cpassword" />
      </Form.Item>
      <Form.Item>
        <LongButton htmlType="submit" type="primary">
          注册
        </LongButton>
      </Form.Item>
    </Form>
  );
};

再接着为 登录注册页 添加异步状态 Loading 的处理:

...
import { useAsync } from "utils/use-async";

export const Login = ({onError}: { onError: (error: Error) => void }) => {
  const { login, user } = useAuth();
  const { run, isLoading } = useAsync()

  const handleSubmit = async (values: { username: string; password: string }) => {
    try {
      // login(values).catch(e => onError(e))
      await run(login(values))
    } catch(e: Error | any) {
      onError(e)
    }
  };
  return (
    <Form onFinish={handleSubmit}>
      ...
      <Form.Item>
        <LongButton loading={isLoading} htmlType="submit" type="primary">
          登录
        </LongButton>
      </Form.Item>
    </Form>
  );
};
...

检验一下,没有效果,但是控制台抛出 400 错误了,排查一下

  • try..catch 中的 onError 没接收到,唯一的变数就是这个 run
  • 查看一下,果然报错被 run 内部消化了,没有正常抛出(将 catch 到的 error throw 或是用 Promise.reject 包裹返回都是可以的,建议使用后者)

修改 src\utils\use-async.ts

...
export const useAsync = <D>(initialState?: State<D>) => {
  ...
  // run 来触发异步请求
  const run = (promise: Promise<D>) => {
    ...
    return promise
      .then(...)
      .catch((error) => {
        // catch 会消化异常,如果不主动抛出,外面是接收不到异常的
        setError(error);
        // return error; // 原代码
        // throw error;
        return Promise.reject(error);
      });
  };
  ...
};

检验一下,正常 catch 并 展示报错信息

  • try…catch only works for runtime errors (try…catch 只能处理有效代码之中的异常)
  • try…catch works synchronously(try…catch 只能处理同步代码之中的异常)

问题是解决了,但这样 try…catch 还是有些拖泥带水的感觉,继续优化:

修改 src\utils\use-async.ts(增加是否抛出异常的配置,来合理化逻辑):

...
const defaultConfig = {
  throwOnError: false
}

export const useAsync = <D>(initialState?: State<D>, initialConfig?: typeof defaultConfig) => {
  const config = {...defaultConfig, ...initialConfig}
  ...

  // run 来触发异步请求
  const run = (promise: Promise<D>) => {
    ...
    return promise
      .then((data) => {
        setData(data);
        return data;
      })
      .catch((error) => {
        // catch 会消化异常,如果不主动抛出,外面是接收不到异常的
        setError(error);
        return config.throwOnError ? Promise.reject(error) : error;
      });
  };
  ...
};

修改 src\unauthenticated-app\login.tsx (传入 { throwOnError: true }):

...
export const Login = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const { run, isLoading } = useAsync(undefined, { throwOnError: true })
  ...
};
...

同理修改 src\unauthenticated-app\register.tsx

...
export const Register = ({onError}: { onError: (error: Error) => void }) => {
  ...
  const { run, isLoading } = useAsync(undefined, { throwOnError: true })

  const handleSubmit = async ({ cpassword, ...values }: { username: string, password: string, cpassword: string }) => {
    if (cpassword === values.password) {
      try {
        await run(register(values))
      } catch (e: Error | any) {
        onError(e)
      }
    } else {
      onError(new Error('请确认两次的输入密码相同'))
      return
    }
  };
  return (
    <Form onFinish={handleSubmit}>
      ...
      <Form.Item>
        <LongButton loading={isLoading} htmlType="submit" type="primary">
          注册
        </LongButton>
      </Form.Item>
    </Form>
  );
};

最后收尾,修改 src\unauthenticated-app\index.tsx (切换登录和注册时,error 清空):

...
export const UnauthenticatedApp = () => {
  const [isRegister, setIsRegister] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  return (
    <Container>
      ...
      <ShadowCard>
        ...
        <Button type="link" onClick={() => { setIsRegister(!isRegister); setError(null) }}>
          切换到{isRegister ? "已经有账号了?直接登录" : "没有账号?注册新账号"}
        </Button>
      </ShadowCard>
    </Container>
  );
};
...

检验效果,完美!

拓展学习(引用自:高薪之路—前端面试精选集-慕课专栏)

js 是单线程的,异步在 js 中是反直觉的存在

判断打印顺序:

console.log('script start')
setTimeout(function(){
  console.log('setTimeout');
},0);
new Promise(function(resolve){
  console.log('promise1');
  resolve();
  console.log('promise2');
}).then(function(){
  console.log('promise then');
});
console log('script end');

打印顺序:

script start
promise1
promise2
script end
promise then
setTimeout

因为JavaScript中有2种任务:

  • 宏任务(macro-task):同步 script(整体代码),setTimeout 回调函数,setlnterval 回调函数,l/O,Ul rendering;
  • 微任务(micro-task):process.nextTick,Promise 回调函数, Object.observe,MutationObserver

其执行的顺序是这样的:

  1. 首先 JavaScript 引擎会执行一个宏任务,注意这个宏任务一般是指主干代码本身,也就是目前的同步代码;
  2. 执行过程中如果遇到微任务,就把它添加到微任务任务队列中;
  3. 宏任务执行完成后,立即执行当前微任务队列中的微任务,直到微任务队列被清空;
  4. 微任务执行完成后,开始执行下一个宏任务;
  5. 如此循环往复,直到宏任务和微任务被清空。

部分引用笔记还在草稿阶段,敬请期待。。。文章来源地址https://www.toymoban.com/news/detail-541164.html

到了这里,关于【实战】 六、用户体验优化 - 加载中和错误状态处理(中) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(九)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 改变用户体验:Whirl动画加载库的无限可能

    哈喽!欢迎来到程序视点。今天小二哥要分享的不是 Animate.js,也不是 Move.js,而是能提供108种加载动画的库: Whirl . 让加载动画变得丰富多彩! 话不多说,直接来看例子。 以上只是冰山一角。whirl的CSS加载动画集合中有108种选项供你挑选。选中喜欢的动画后,点击“Grab th

    2024年02月06日
    浏览(52)
  • vue前端开发自学,异步加载组件,提升用户端的客户体验度

    vue前端开发自学,异步加载组件,提升用户端的客户体验度!现实项目开发时,组件的数量非常庞大,如果都是一口气加载完,对手机用户来说,体验度会很差。因此,非常有必要使用异步加载。 那就是,用到了哪个组件,再去加载它就行了。用不到的时候,不加载它。下面看

    2024年01月16日
    浏览(46)
  • 记一次rax应用用户体验性能优化

    对于前端开发攻城狮们来说,性能优化是一个永恒的话题。随着前端需求复杂度的不断升高,在项目中想始终保持着良好的性能也逐渐成为了一个有挑战的事情。本次分享简述我们在 Rax 项目中常用的一些性能优化方式,并将从近期的一个实际业务需求出发,讲述我在 Rax C端

    2024年02月21日
    浏览(46)
  • uniapp 中 的progress加载进度条 的使用,在 页面显示数据加载的进度条,使用户的使用体验效果更好

    学习目标如下: 例如: uniapp 中 的progress加载进度条 的使用,在 页面显示数据加载的进度条,使用户的使用体验效果更好 学习内容如下所示: 相关属性的说明 进度条的显示 是否显示属性的控制 显示进度条 进度条的样式设置 提示:这里统计学习计划的总量 1、进度条的显

    2024年02月15日
    浏览(62)
  • 医疗小程序:让服务更高效,用户体验更优化

    随着移动互联网的快速发展,小程序已经成为了一个热门的开发方向。医疗健康类小程序也不例外,拥有广泛的市场需求和前景。本文将为你提供一份完整的医疗健康类小程序开发攻略,帮助你快速开发上线一个专业成熟的小程序商城。 一、选择合适的小程序商城制作平台

    2024年02月10日
    浏览(42)
  • 网站优化指南:提升用户体验与搜索引擎排名

    💂 个人网站:【海拥】【游戏大全】【神级源码资源网】 🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】 💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 拥有一个优化的网站对于吸引用户、提升用户体验以及在搜索引擎中获得更好的排名至

    2024年02月12日
    浏览(136)
  • Solr在搜索引擎中的用户体验优化

    作者:禅与计算机程序设计艺术 引言 1.1. 背景介绍 搜索引擎是互联网时代最为基础的应用之一,对于用户体验的要求也越来越高。搜索引擎的性能与稳定性、搜索结果的准确性和多样性、搜索结果的相关性等方面都会影响着用户的体验。而Solr是一款高性能、可扩展、易于使

    2024年02月13日
    浏览(55)
  • 优化您的服务请求,增强用户体验和服务交付

    您的服务请求模板是否像一个复杂的迷宫,给您的团队带来延误和困惑?您的技术人员是否厌倦了为了解最终用户的需求而与他们来回奔波?强大且可定制的请求模板可能正是您所需要的! 服务交付团队(尤其是 IT)的用户可以通过各种渠道(如电子邮件、电话、聊天和步入

    2024年01月16日
    浏览(36)
  • C端用户体验度量实战篇-京东快递小程序体验度量全面升级

    本文通过介绍体验度量模型升级研究过程、研究方法及研究结果等内容,结合实际C端产品应用,观测新模型运行周期的表现,验证了其在高速发展的业务形态和日益变化的用户需求上的适用性和有效性。我们从体验价值为导向的底层模型设计,到主客观体验影响因子在实际业

    2024年02月07日
    浏览(42)
  • 【PyQt5实现多线程更新UI】- 提高程序效率,优化用户体验

    【PyQt5实现多线程更新UI】- 提高程序效率,优化用户体验 在PyQt5应用程序的开发中,当程序需要处理大量数据或进行复杂的计算时,如果仅使用主线程,会导致GUI界面失去响应,用户体验较差。为了解决这个问题,通常需要使用多线程技术。 而在使用多线程时,往往需要更新

    2024年02月07日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包