博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于 React Redux 的错误处理
阅读量:6371 次
发布时间:2019-06-23

本文共 8384 字,大约阅读时间需要 27 分钟。

本文主要分为以下三个部分:

  • Error 的分类
  • 分步骤详细讲解如何利用 Redux 统一处理 Error
  • 错误信息的收集

本文的案例使用的技术栈包括: ReactReduxTypeScriptAxiosLodash

Error 的分类

HTTP 请求错误

HTTP 请求错误通常可以归为以下几类:

服务器有响应的错误

服务器有响应,表示服务器响应了,并且返回了相应的错误信息

如果你不期望每一个请求都显示服务器返回的特定错误信息,还可以根据 HTTP Status Code 对错误信息进一步归类:

  • 4xx客户端错误: 表示客户端发生了错误,妨碍了服务器的处理。比如:

    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
    • 408 Request Timeout
    • 409 Conflict
  • 5xx服务器错误: 表示服务器无法完成合法的请求。可能是服务器在处理请求的过程中有错误或者异常状态发生。比如:

    • 500 Internal Server Error
    • 501 Not Implemented
    • 503 Service Unavailable

服务器无响应的错误

服务器无响应,表示请求发起了,但是服务器没有响应

这种情况可能是因为网络故障(无网/弱网),或着跨域请求被拒绝(生产环境通常不会有跨域的情况出现,所以这个错误一般不用考虑)。如果你使用的 HTTP Client 没有返回错误信息,可以考虑显示一个通用错误信息(General Error Message)。

应用程序错误

代码错误

通常是由于 JS 代码编写错误,导致 JavaScript 引擎无法正确执行,从而报错。这一类错误在生产环境一般不会出现,因此可以根据业务需求决定是否处理这一类错误。常见的有:

  • SyntaxError语法错误
  • ReferenceError引用错误
  • TypeError类型错误

Throw Error

应用中根据业务需求而 Throw 的 Error。

Redux 中的 Error 处理

在上面的章节中我们已经对应用中的 Error 进行了分类。 利用 Redux 我们可以对 HTTP Request Error 进行统一的处理。

Step1: 裂变 HTTP Request Action

在进行 HTTP 请求的时候,我们通常会发起一个 Action。如果将请求成功和失败的状态裂变成两个 Action,RequestSuccessActionRequestFailedAction,那么通过 RequestFailedAction,就能够对所有 HTTP 请求的错误进行统一处理。

requestMiddleware.ts

export const requestMiddleware: any = (client: AxiosInstance) => {  return ({ dispatch }: MiddlewareAPI
) => (next: Dispatch
) => (action: IRequestAction) => { if (isRequestAction(action)) { dispatch(createReqStartAction(action)); return client.request(action.payload) .then((response: AxiosResponse) => { return dispatch(createSuccessAction(action, response)); }) .catch((error: AxiosError) => { return dispatch(createFailedAction(action, error, action.meta.omitError)); }); } return next(action); };};

Step2: 创建 errorMiddleware,将 Error 转化为 Notification Action

将 HTTP 请求的失败状态转化成 RequestFailedAction 之后,我们需要写一个 Middleware 来处理它。

这里有人可能会问了,既然已经有 RequestFailedAction 了,还需要 Middleware 吗?能不能直接在 Reducer 中去处理它?其实也是可以的。但是写在 Reducer 里面,同一个 Action 修改了多个 State 节点,会导致代码耦合度增加,所以在这里我们还是使用 Middleware 的方式来处理。思路如下:

  1. 如果 Action 是一个 RequestFailedAction,那么根据错误的分类,将错误的类型和信息存储到 addNotificationAction 中。在这里我们并不需要将所有的错误信息都存起来,因为 UI 只关心 Error 的类型和信息。
  2. 根据 Error 的分类,Dispatch 带有不同 Error Type 和 Error Message 的 Action。
  3. 创建 createNotification 函数,生成一个带有 UUID 的 Notification,以便删除时使用。因为 notification 可能不止一个。
  4. 通过 Dispatch removeNotificationAction 来移除 Notification。
export interface INotification {  [UUID: number]: {    type: string;    msg: string;  };}const createNotification = ({ type, msg }: { type: string; msg: string }): INotification => {  const id = new Date().getTime();  return {    [id]: {      type,      msg,    },  };};

完整代码如下:

errorMiddleware.ts

import { AnyAction, Dispatch, MiddlewareAPI } from "redux";import { isRequestFailedAction } from "../request";import {  addNotification,  INotification,} from "./notificationActions";export enum ErrorMessages {  GENERAL_ERROR = "Something went wrong, please try again later!",}enum ErrorTypes {  GENERAL_ERROR = "GENERAL_ERROR",}export const createNotification = ({ type, msg }: { type: string; msg: string }): INotification => {  const id = new Date().getTime();  return {    [id]: {      type,      msg,    },  };};export const errorMiddleware = ({ dispatch }: MiddlewareAPI) => {  return (next: Dispatch
) => { return (action: AnyAction) => { if (isRequestFailedAction(action)) { const error = action.payload; if (error.response) { dispatch( addNotification( createNotification({ type: error.response.error, msg: error.response.data.message, }), ), ); } else { dispatch( addNotification( createNotification({ type: ErrorTypes.GENERAL_ERROR, msg: ErrorMessages.GENERAL_ERROR, }), ), ); } } return next(action); }; };};

notificationActions.ts

import { createAction } from "redux-actions";export interface INotification {  [UUID: number]: {    type: string;    msg: string;  };}export const addNotification = createAction(  "@@notification/addNotification",  (notification: INotification) => notification,);export const removeNotification = createAction("@@notification/removeNotification", (id: number) => id);export const clearNotifications = createAction("@@notification/clearNotifications");
服务器需要保证每一个 HTTP Reqeust 都有相应的 Error Message,不然前端就只能根据 4xx 或者 5xx 这种粗略的分类来显示 Error Message。

Step3: 处理 Notification Action

notificationsReducer.ts

import { omit } from "lodash";import { Action, handleActions } from "redux-actions";import { addNotification, clearNotifications, removeNotification } from "./notificationActions";export const notificationsReducer = handleActions(  {    [`${addNotification}`]: (state, action: Action
) => { return { ...state, ...action.payload, }; }, [`${removeNotification}`]: (state, action: Action
) => { return omit(state, action.payload); }, [`${clearNotifications}`]: () => { return {}; }, }, {},);

Step4: 从 Store 中获取 Notification,并通过 React Child Render 提供给子组件。

这一步就很简单了,从 Store 中拿到 Notifications,然后通过 React Child Render 将它提供给子组件,子组件就可以根据它去显示 UI 了。

WithNotifications.tsx

import { isEmpty } from "lodash";import * as React from "react";import {  connect,  DispatchProp,} from "react-redux";import {  clearNotifications,  INotification,} from "./notificationActions";interface IWithNotificationsCoreInnerProps {  notifications: INotification;}interface IWithNotificationsCoreProps extends DispatchProp {  notifications: INotification;  children: (props: IWithNotificationsCoreInnerProps) => React.ReactNode;}class WithNotificationsCore extends React.Component
{ componentWillUnmount() { this.props.dispatch(clearNotifications()); } render() { if (isEmpty(this.props.notifications)) { return null; } return this.props.children({ notifications: this.props.notifications, }); }}const mapStateToProps = (state: any) => { return { notifications: state.notifications, };};export const WithNotifications = connect(mapStateToProps)(WithNotificationsCore);

Step5: 显示 Error Messages

因为 Notification 是一个通用的组件,所以我们一般会把它放到根组件 (Root) 上。

{({ notifications }) => ( <> {map(notifications, (notification: { type: string; msg: string }, id: number) => { return (
{notification.msg} // 将你的 Notification 组件放到这里 {id} // 你可以用 id 去删除对应的 Notification
); })} )}

Step6: 添加白名单

当然,并不是所有的 API 请求出错我们都需要通知给用户。这时候你就需要加一个白名单了,如果在这个白名单内,则不将错误信息通知给用户。可以考虑在 Requst Action 的 Meta 中加一个 omitError 的 flag,当有这个 flag 的时候,则不进行通知。让我们修改一下 errorMiddleware,如下:

errorMiddleware.ts

export const errorMiddleware = ({ dispatch }: MiddlewareAPI) => {  return (next: Dispatch
) => { return (action: AnyAction) => { const shouldOmitError = get(action, "meta.omitError", false); if (isRequestFailedAction(action) && !shouldOmitError) { const error = action.payload; if (error.response) { // same as before } else { // same as before } return next(action); }; };};

Step7: 测试

在测试 errorMiddleware 的时候,可能会遇到一个问题,就是我们的 Notification 是根据一个以时间戳为 key 的对象,时间戳是根据当前时间生成的,每次跑测试时都会发生变化,如何解决呢?Mock getTime 方法就好啦。如下:

beforeEach(() => {    class MockDate {      getTime() {        return 123456;      }    }    global.Date = MockDate as any;  });  afterEach(() => {    global.Date = Date;  });

错误信息的收集

componentDidCatch

利用 React componentDidCatch 生命周期方法将错误信息收集到 Error Reporting 服务。这个方法有点像 JS 的 catch{},只不过是针对组件的。大多数时候我们希望 ErrorBoundary 组件贯穿我们的整个应用,所以一般会将它放在根节点上 (Root)。

class ErrorBoundary extends React.Component {  constructor(props) {    super(props);    this.state = { hasError: false };  }  componentDidCatch(error, info) {    // Display fallback UI    this.setState({ hasError: true });    // You can also log the error to an error reporting service    logErrorToMyService(error, info);  }  render() {    if (this.state.hasError) {      // You can render any custom fallback UI      return 

Something went wrong.

; } return this.props.children; }}
注意:对 ErrorBoundary 组件来说,它只会捕获在它之下的组件,它不会捕获自身组件内部的错误。

转载地址:http://niyqa.baihongyu.com/

你可能感兴趣的文章
工作代码备用
查看>>
spring cloud互联网分布式微服务云平台规划分析--spring cloud定时调度平台
查看>>
说说如何配置 Webpack
查看>>
小程序中使用箭头函数的问题
查看>>
走进 JDK 之 Long
查看>>
Android打地鼠游戏的修改和优化
查看>>
Java异常
查看>>
map、reduce、filter、for...of、for...in等总结
查看>>
html2canvas-实现页面截图
查看>>
入门 | 从文本处理到自动驾驶:机器学习最常用的50大免费数据集
查看>>
笔记-从源码角度分析alloc与init的底层
查看>>
消除GitHub上的历史记录
查看>>
自学 JAVA 的几点建议
查看>>
第十三天-企业应用架构模式-对象-关系元数据映射模式
查看>>
k8s与HPA--通过 Prometheus adaptor 来自定义监控指标
查看>>
虎牙直播在微服务改造方面的实践和总结
查看>>
怎样将优酷网站下载的视频KUX转MP4格式
查看>>
MongoDB 分组统计
查看>>
二进制状态码
查看>>
Vue 中 CSS 动画原理
查看>>