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显示错误,这样更加抽象和灵活。
我在 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显示错误,这样更加抽象和灵活。