From 1143224194fdcd9c745a8bc79b8cf673a3e6824f Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Sat, 20 Jun 2026 18:26:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(common):=20=E5=AF=B9=E9=BD=90=20@szyx-mobil?= =?UTF-8?q?e=20useRequest=20=E8=A1=8C=E4=B8=BA=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20POST=20body=20=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 useRequest POST/PUT/UPLOAD/POSTFORM 请求忽略 fetchAsync data 的问题 - RequestOptions 增加 tag/log 字段,与旧项目兼容 - 导出 ValidationError,供宿主二次封装使用 instanceof 判断 - usePageApi 支持 list 与 records 两种分页字段 - 升级 devDependencies 版本(axios 1.18.0、zod 3.25.76 等) Co-Authored-By: Claude --- packages/common/package.json | 12 ++--- packages/common/src/api/index.ts | 2 +- packages/common/src/api/useApi.ts | 65 ++++++++++++++++----------- packages/common/src/api/usePageApi.ts | 23 +++++++--- packages/common/src/api/useRequest.ts | 36 +++++++++++++-- packages/common/src/index.ts | 1 + 6 files changed, 97 insertions(+), 42 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index 6992b76..3e45ae0 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -26,11 +26,11 @@ }, "devDependencies": { "typescript": "^5.9.3", - "@types/react": "^19.0.0", - "@react-native-async-storage/async-storage": "^2.1.2", - "axios": "^1.7.0", - "react": "^19.0.0", - "react-native": "^0.85.0", - "zod": "^3.23.0" + "@types/react": "^19.2.14", + "@react-native-async-storage/async-storage": "^3.1.1", + "axios": "^1.18.0", + "react": "^19.2.0", + "react-native": "^0.85.3", + "zod": "^3.25.76" } } diff --git a/packages/common/src/api/index.ts b/packages/common/src/api/index.ts index da191ae..cec995b 100644 --- a/packages/common/src/api/index.ts +++ b/packages/common/src/api/index.ts @@ -1,4 +1,4 @@ -export { useRequest } from './useRequest' +export { useRequest, ValidationError } from './useRequest' export { useApi } from './useApi' export { usePageApi } from './usePageApi' export { RequestError } from './errors' diff --git a/packages/common/src/api/useApi.ts b/packages/common/src/api/useApi.ts index a67266d..1170a5a 100644 --- a/packages/common/src/api/useApi.ts +++ b/packages/common/src/api/useApi.ts @@ -8,7 +8,7 @@ import { } from 'axios' import { z } from 'zod' import { RequestError } from './errors' -import { type RequestOptions, useRequest } from './useRequest' +import { type RequestOptions, useRequest, ValidationError } from './useRequest' const SUCCESS_STATUS = /^0$/ @@ -20,21 +20,6 @@ type ApiResponse = { export type ApiMethod = 'GET' | 'POSTJSON' | 'POSTFORM' | 'PUT' | 'UPLOAD' -// ValidationError shape (duck-typed to avoid circular dependency) -interface HasIssues { - issues: Array<{ path: (string | number)[]; message: string }> - response?: { data?: { message?: string } } -} - -function isValidationError(e: unknown): e is HasIssues { - return ( - e != null && - typeof e === 'object' && - 'issues' in e && - Array.isArray((e as { issues: unknown }).issues) - ) -} - export const useApi = < S extends z.ZodTypeAny, T extends z.infer = z.infer, @@ -97,7 +82,12 @@ export const useApi = < useEffect(() => { const responseInterceptor = responseInterceptors.use( - nextResponse => nextResponse, + r => { + if (options?.log) { + console.debug(JSON.stringify(r, null, 2)) + } + return r + }, (thrownError: unknown) => { if (isCancel(thrownError)) { return Promise.reject( @@ -105,45 +95,68 @@ export const useApi = < ) } - if (isValidationError(thrownError)) { - if (thrownError.issues.length <= 0) { + if (thrownError instanceof ValidationError) { + const e = thrownError + console.error( + JSON.stringify( + { + name: e.name, + status: e.response.status, + data: e.response.data, + config: e.response.config, + headers: e.response.headers, + issues: e.issues, + url: `${e.response.config.baseURL}${e.response.config.url}`, + }, + null, + 2, + ), + ) + + if (e.issues.length <= 0) { return Promise.reject( - new RequestError('数据格式校验错误', 'ValidationError', thrownError), + new RequestError('数据格式校验错误', 'ValidationError', e), ) } - const firstIssue = thrownError.issues[0] + const firstIssue = e.issues[0] if (firstIssue.path.length > 0 && firstIssue.path[0] === 'status') { return Promise.reject( new RequestError( - thrownError.response?.data?.message ?? '接口返回失败', + e.response.data?.message ?? '接口返回失败', 'ValidationError', - thrownError, + e, ), ) } return Promise.reject( - new RequestError(firstIssue.message, 'ValidationError', thrownError), + new RequestError(firstIssue.message, 'ValidationError', e), ) } if (isAxiosError(thrownError)) { + console.error( + JSON.stringify(thrownError, null, 2), + `${thrownError.config?.baseURL}${thrownError.config?.url}`, + ) return Promise.reject( new RequestError('网络请求失败', 'AxiosError', thrownError), ) } + console.error( + JSON.stringify(thrownError, null, 2), + ) return Promise.reject( new RequestError('网络请求失败', 'OtherError', thrownError), ) }, ) - return () => { responseInterceptors.eject(responseInterceptor) } - }, [responseInterceptors]) + }, [options?.log, responseInterceptors]) const transformedFetchAsync = useCallback( async (nextConfig?: AxiosRequestConfig) => { diff --git a/packages/common/src/api/usePageApi.ts b/packages/common/src/api/usePageApi.ts index 9a9702f..260cf9e 100644 --- a/packages/common/src/api/usePageApi.ts +++ b/packages/common/src/api/usePageApi.ts @@ -7,7 +7,8 @@ import { z } from 'zod' import { useApi } from './useApi' type List = { - records: T[] + records?: T[] + list?: T[] } type Root = T[] @@ -41,7 +42,8 @@ export const usePageApi = < const [status, setStatus] = useState<'loaded' | 'reload' | 'loadmore'>('loaded') const listSchema: z.ZodSchema> = z.object({ - records: z.array(validationSchema ?? z.any()), + records: z.array(validationSchema ?? z.any()).optional(), + list: z.array(validationSchema ?? z.any()).optional(), }) const rootSchema: z.ZodSchema> = z.array(validationSchema ?? z.any()) @@ -58,7 +60,12 @@ export const usePageApi = < status === 'loadmore' ? Math.ceil((data ?? []).length / (options?.pageSize ?? 10)) + 1 : 1, + pageNum: + status === 'loadmore' + ? Math.ceil((data ?? []).length / (options?.pageSize ?? 10)) + 1 + : 1, size: options?.pageSize ?? 10, + pageSize: options?.pageSize ?? 10, } : { ...params, @@ -90,9 +97,11 @@ export const usePageApi = < case 'root': setData(result as Root) break - default: - setData((result as List).records) + default: { + const listResult = result as List + setData(listResult.records ?? listResult.list) break + } } setStatus('loaded') return @@ -102,12 +111,14 @@ export const usePageApi = < case 'root': setData(previous => [...(previous ?? []), ...(result as Root)]) break - default: + default: { + const listResult = result as List setData(previous => [ ...(previous ?? []), - ...(result as List).records, + ...(listResult.records ?? listResult.list ?? []), ]) break + } } setStatus('loaded') }) diff --git a/packages/common/src/api/useRequest.ts b/packages/common/src/api/useRequest.ts index 9ee1be7..80432dd 100644 --- a/packages/common/src/api/useRequest.ts +++ b/packages/common/src/api/useRequest.ts @@ -18,11 +18,15 @@ export interface RequestOptions { manual?: boolean /** 延迟多少毫秒后才将 loading 置为 true(默认 0)。*/ loadingDelay?: number + /** 请求标签,用于日志或错误追踪。*/ + tag?: string + /** true = 在响应拦截器中打印完整响应。*/ + log?: boolean } // ─── 内部 ValidationError(对齐 @szyx-mobile/use-axios 的 ValidationError)──── -class ValidationError extends z.ZodError { +export class ValidationError extends z.ZodError { constructor( issues: z.ZodIssue[], public readonly response: AxiosResponse, @@ -127,6 +131,28 @@ export function useRequest< } } + function mergeRequestData(base: unknown, override: unknown): unknown { + if (override === undefined || override === null) { + return base + } + + if (base === undefined || base === null) { + return override + } + + if ( + typeof base === 'object' && + typeof override === 'object' && + !(override instanceof FormData) && + !(override instanceof ArrayBuffer) && + !Array.isArray(override) + ) { + return {...base as Record, ...override as Record} + } + + return override + } + const fetchAsync = useCallback( async (overrideConfig?: AxiosRequestConfig): Promise => { const loadingDelay = optionsRef.current?.loadingDelay ?? 0 @@ -152,8 +178,12 @@ export function useRequest< ...overrideConfig, url, method: axiosMethod, - params: isGet ? params : (overrideConfig?.params ?? config?.params), - data: isGet ? (overrideConfig?.data ?? config?.data) : params, + params: isGet + ? (overrideConfig?.params ?? config?.params ?? params) + : (overrideConfig?.params ?? config?.params), + data: isGet + ? (overrideConfig?.data ?? config?.data) + : mergeRequestData(config?.data, overrideConfig?.data ?? params), headers: { ...methodHeaders, ...(config?.headers as Record | undefined), diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 547f04c..45b2095 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -47,6 +47,7 @@ export { useRequest, useApi, usePageApi, + ValidationError, } from './api' export type { RequestOptions as UseRequestOptions, ApiMethod } from './api' export type { PageOptions } from './api'