React - 处理 useState() 时出错。如何重置错误?

React - Error handling useState(). How to reset errors?

简介

我正在处理我的应用程序中的所有错误。我注意到 React 可以处理不同类型的错误...

  1. 渲染错误(未定义的道具,...),可以使用 Error Boundary Component 和自定义回退组件来处理。

  2. 来自后端的错误,我想用 i18n.js 显示在 toast 中以翻译消息。

在 React 应用程序中结合这两种类型的错误处理是否很常见?

在 toast 中显示错误

我决定创建自己的 React Context 来为通知、错误等呈现 toasts。这是我的实现:

/* eslint-disable react/prop-types */
import React, {
  createContext,
  useReducer,
  useMemo,
} from "react";

import toastReducer, {
  initialState,
  actionCreators,
} from "./helpers/reducers/toastReducer";
import { TOAST_TYPES, DEFAULT_TOAST_DURATION } from "../../utils/toast";
import Toast from "../../components/Toast";

const ToastContext = createContext(null);

export default ToastContext;

export function ToastProvider({ children }) {
  const [toast, dispatch] = useReducer(toastReducer, initialState);

  const open = (
    type = TOAST_TYPES[0],
    message,
    duration = DEFAULT_TOAST_DURATION,
    action = undefined
  ) => {
    dispatch(actionCreators.open(type, message, duration, action));
  };

  const close = () => {
    dispatch(actionCreators.close());
  };

  const value = useMemo(() => ({
    open,
    close,
  }), []);

  return (
    <ToastContext.Provider value={value}>
      {children}
      <Toast
        visible={toast.visible}
        type={toast.type}
        duration={toast.duration}
        action={toast.action}
        onDismiss={close}
      >
        {toast.message}
      </Toast>
    </ToastContext.Provider>
  );
}

我还实现了使用此上下文的 useToast() 挂钩。

正在从服务器获取数据

因为我正在使用挂钩,所以我将错误保存在每个从我的数据库中获取数据的自定义挂钩上的状态“错误”中。

// Based on the library "useQuery"
const useFetchSomeData = ({ fetchOnMount = true } = {}) => {
   const [data, setData] = useState([]);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(null);   

   const cursor = useRef(new Date());
   const isFetching = useRef(false);

   const fetchData = async () => {
      if (isFetching.current) return;

      isFetching.current = true;

      setLoading(true);

      try {
         const data = await api.fetchData(cursor.current);
      } catch(err) {
         setError(err);
      } finally {
         setLoading(false);

         isFetching.current = false;
      }
   }

   useEffect(() => {
     if (fetchOnMount) {
        fetchData();
     }
   }, []);

   return {
      data,
      loading,
      error,
      fetchData
   }
}

在我的组件中我只是这样做:

const { data, error, loading } = useFetchData();

const toast = useToast();

const { t } = useI18N();

useEffect(() => {
  if (error) {
     toast.open("error", t(err.code));
  }
}, [error, t]);

问题

  1. 如您所见,在我的组件中,我只是使用 useEffect 将翻译后的错误发送到我的 toast 上下文。但是……如果我两次从服务器得到相同的错误响应怎么办?我的意思是,自定义挂钩中的错误未重置为 null...我该怎么做?

  2. 这个实现是否稳健(在您看来)并且在这些情况下很典型?

老实说,我有点困惑,我想我明白你想做什么,但不一定要这么复杂。

你应该用 ToastContainerToastContext(随便你怎么称呼它)包装你的整个应用程序,并在需要时导入你的 toast,然后调用 Toat.show("Error: ...")。查看图书馆 react-toastify 了解更多详情,也许这个图书馆可以帮助你。您将项目包装在应用程序入口级别,再也不用担心了。

就错误处理而言,在创建一个获取数据的函数时,您应该一次预料到一个错误并适当地处理它。我个人认为最好把错误return传给调用fetch函数的原组件,在那里处理。在我看来,这样更容易跟踪事情,但你所做的并没有错。

错误处理需要时间和超前思考,我不会否认,但它在构建糟糕的应用程序和经过深思熟虑的应用程序之间有很大的不同。

我也不完全确定你为什么要使用 const isFetching = useRef(false); 而你可以只使用 const [isFetching, setIsFecthing] = useState(false),这就是 useState 的用途。 useMemo 也一样。我认为这里的事情可以更精简,因为你的应用程序会增长并变得更复杂,它更容易维护。