【实战】 六、用户体验优化 - 加载中和错误状态处理(中) —— React17

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

先修改 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. 如此循环往复,直到宏任务和微任务被清空。

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

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值