React自定义Hook调用API无法触发catch块,如何解决?
问题
我尝试创建一个自定义Hook,希望在API调用时捕获错误以更改页面显示。以下是自定义Hook代码:
function useBook() { const [book, setBook] = useState<Book | any>() const [loading, setLoading] = useState(true) const [error, setError] = useState<Error | boolean>(false) useEffect(() => { const fetchData = async () => { try { const data = await getBook(Number(Object.values(id))) setBook(data) setLoading(false) } catch (error) { setError(true) setLoading(false) } } fetchData() dispatch({ type: 'auth/isLogin' }) }, []) return { book, loading, error } }
API函数代码如下:
export type Book = { title: string id: number authorName: string authorSurname: string coverImage: string releaseDate: string pages: number price: number } export type Error = { isError: boolean } async function getBook(id: number): Promise<Book | Error> { try { const { data } = await axios.get<Book>( `http://localhost:3000/books/${id}`, { headers: { Accept: 'application/json', }, } ) return data } catch (error) { console.log('Error is : ', error) return { isError: true, } } }
目前的问题是永远无法进入catch错误分支,导致error状态始终为false,请问该如何修改?
解决方案
核心问题在于getBook函数内部已经捕获了所有异常并返回了一个错误对象,而非抛出错误,导致外部的try/catch没有异常可捕获。可以通过以下两种方式修复:
方案1:让API函数抛出错误而非返回错误对象
修改getBook函数,在捕获到axios错误时直接抛出错误,这样外部的try/catch就能正常捕获:
// 重命名自定义错误类型,避免和全局Error冲突 export type ApiError = { isError: boolean } async function getBook(id: number): Promise<Book> { try { const { data } = await axios.get<Book>( `http://localhost:3000/books/${id}`, { headers: { Accept: 'application/json', }, } ) return data } catch (error) { console.log('Error is : ', error) // 抛出错误,让外部Hook捕获 throw error } }
同时调整自定义Hook中的错误状态类型,优化类型定义与逻辑:
function useBook() { const [book, setBook] = useState<Book | undefined>() const [loading, setLoading] = useState(true) // 用null表示无错误,Error对象存储错误信息 const [error, setError] = useState<Error | null>(null) useEffect(() => { const fetchData = async () => { try { const data = await getBook(Number(Object.values(id))) setBook(data) setError(null) // 请求成功时清空错误 } catch (error) { setError(error as Error) setBook(undefined) // 请求失败时清空数据 } finally { // 无论成功失败都要结束加载状态 setLoading(false) } } fetchData() dispatch({ type: 'auth/isLogin' }) }, [id, dispatch]) // 补充依赖项,避免闭包问题 return { book, loading, error } }
方案2:在Hook中手动判断返回的错误对象
如果不想修改API函数的逻辑,就在调用getBook后检查返回值是否为错误对象,手动设置错误状态:
// 先重命名自定义错误类型,避免冲突 export type ApiError = { isError: boolean } async function getBook(id: number): Promise<Book | ApiError> { // 原有逻辑保持不变 try { const { data } = await axios.get<Book>( `http://localhost:3000/books/${id}`, { headers: { Accept: 'application/json', }, } ) return data } catch (error) { console.log('Error is : ', error) return { isError: true, } } }
然后修改自定义Hook中的逻辑:
function useBook() { const [book, setBook] = useState<Book | undefined>() const [loading, setLoading] = useState(true) const [error, setError] = useState(false) useEffect(() => { const fetchData = async () => { try { const data = await getBook(Number(Object.values(id))) if ('isError' in data && data.isError) { // 识别到错误对象,设置错误状态 setError(true) setBook(undefined) } else { // 请求成功,设置数据并清空错误 setBook(data as Book) setError(false) } } catch (error) { // 捕获getBook内部未处理的异常(比如类型转换错误) setError(true) setBook(undefined) } finally { setLoading(false) } } fetchData() dispatch({ type: 'auth/isLogin' }) }, [id, dispatch]) return { book, loading, error } }
内容的提问来源于stack exchange,提问作者Silent observer
相关产品推荐
相关产品推荐

