React Custom Hooks - 处理错误

React Custom Hooks - Handling errors

我在 toast.

中显示我所有的 api 请求错误

在我的代码中,我分离了概念,将组件逻辑移至 business/ui 挂钩。

为了渲染 toast(命令式组件),我只是在功能组件中执行以下操作:

const toast = useToast(); // UI hook

toast.display(message, { type: "error", duration: 500 });

并且,为了连接到我的 api,我可以使用自定义业务挂钩,例如:

const useRequestSomething() {
   const [data, setData] = useState([]);
   const [isLoading, setIsLoading] = useState(false);

   const isRequesting = useRef(false);

   const requestSomething = async (someParam, onSuccess = undefined, onError = undefined) => {
      if (isRequesting.current) return;

      isRequesting.current = true;

      setIsLoading(true);

      try {
         const data = await api.requestSomething(someParam);
         setData(data);
         onSuccess?.();
      } catch(err) {
         onError?.(err);
      }

      setIsLoading(false);

      isRequesting.current = false;
   }

   return {
      data,
      isLoading,
      requestSomething
   }
}

我主要关心的是概念的分离...我认为在作为我的业务逻辑容器的 this 挂钩内使用 useToast() 不是一个好主意...虽然它可能是个好主意。

所以,为了处理错误,在任何组件内部,我可以做类似的事情:

function MyComponent() {
   const toast = useToast();

   const { t } = useTranslation(); // i18n.js hook

   const { data, isLoading, requestSomething } = useRequestSomething(); 

   const handleOnPress = () => {
      requestSomething("x", undefined, handleOnRequestSomethingError);
   }

   const handleOnRequestSomethingError = (err) => {
      toast.display(t(err), { type: "error", duration: 500 });
   }

   ... JSX
} 

看来我已经定义了某种基于回调的业务挂钩 api...您如何看待我的实现?

在 hooks 中以这种方式(使用回调)处理错误是一种反模式吗?

处理这种情况的典型方法是什么? (我不能使用 useQuery,因为我的后端)

我认为您的解决方案很好,但是,恕我直言,我不想过早地处理错误,而是希望让错误传播到我们真正知道如何处理它的地方。例如,我会这样做。

const requestSomething = async (params) = {
   ...
   try {
       await api.doRequest(params);
   } catch (err) {
       ... do some common clean up ...
       throw err;
   }
}
const handleOnPress = async () => {
   try {
      await requestSomething("x");
   } catch (err) {
      toast.display(t(err), { type: "error", duration: 500 });
   }
  
}

实际上,我会将其包装在像这样的通用错误处理程序中。

const handleOnPress = async () => {
   await withGeneralErrorHandling(async () => {
      try {
         await requestSomething("x");
      } catch (err) {
         if (err.errorCode === 'SOME_KNOWN_CASE') {
            toast.display(t(err), { type: "error", duration: 500 });
         } else {
            throw err;
         }
      }
   });
}

async function withGeneralErrorHandling(callback: () => Promise<void>) {
   try {
       await callback()
   } catch (err) {
       if (err.errorCode === 'GENERAL_CASE1') { ...}
       else if (err.errorCode === 'GENERAL_CASE2') { ... }
       else {
          if (isProduction) { reportError(err); }
          else { throw err; }
       }
   }
}

这是因为我通常无法在第一次执行时列出所有错误情况。将逐步发现每个错误案例。我必须让它传播到尽可能靠近最外面的控制器,让它快速失败。

通过利用这种内置的错误传播,您可以保留堆栈跟踪信息并可以准确知道错误发生的位置。

是的,您的组件知道 Toast,每个处理某些错误的未来组件都会知道 Toast

这让你的错误处理逻辑有点死板,如果你以后需要使用其他方式处理错误,你将不得不编辑每个组件。 我会使用一些状态管理系统(redux、mobx 等)。 这个想法是,为了显示错误,您需要更新应用程序的状态。您的 toast 组件将订阅状态更改并做出相应反应。

这种方式依赖于状态,而不是一些实际的component/way显示错误,这样更加抽象和灵活。