@gz-fronted/gz-pc
v0.2.0
Published
Shared fetch, hooks and utility primitives for GZ frontend applications.
Readme
@gz-fronted/gz-pc
面向 GZ 前端项目的请求、Hooks 和轻量工具函数包。各子路径独立构建;只使用
utils 时不会加载 Axios、React、ahooks 或 gz-ui-web。
安装
npm install @gz-fronted/gz-pc react@^19 react-dom@^19 [email protected] [email protected] [email protected] [email protected] [email protected]宿主项目需要提供以下 peer dependencies:
- React 19
gz-ui-web^1.0.1(开发验证固定使用 1.0.1)
[email protected] 还要求宿主提供 React DOM 19、AntD 6.3.3,以及
ag-grid-react、ag-grid-community、ag-grid-enterprise 36.0.1。
在应用入口引入一次组件库样式:
import 'gz-ui-web/gz-ui-web.css';从旧版迁移时,宿主需要将组件导入和 ConfigProvider 统一切换到 gz-ui-web,
并将旧样式入口替换为上述入口。本次 peer 依赖迁移包含兼容性变化;React 18 项目需要先升级。
Axios、ahooks 和 lossless-json 是 gz-pc 的普通 dependencies,安装 gz-pc 时会自动安装,宿主 无需手工添加。React 和 gz-ui-web 不会打入本包产物。
本包刻意不提供聚合根入口,请始终从 /fetch、/hooks 或 /utils 子路径导入,
避免加载无关模块。
请求协议
gz-fetch 默认将所有 HTTP 2xx 状态视为成功,包括 200、201、202、204 和 206。
非 2xx 状态进入统一错误处理并抛出 GzFetchError,错误中保留 status 和
responseData。
成功时直接返回后端原始的 response.data,不会返回 AxiosResponse,也不会:
- 判断业务
code - 自动解包
data - 转换分页、字段或日期(数值精度保护需在初始化时显式开启)
例如后端返回以下内容时,调用方取得的是包含 code、data、message 的完整
对象:
interface CreateOrderResponse {
code: number;
data: {
orderId: string;
};
message: string;
}
const response = await gzFetch<CreateOrderResponse, CreateOrderParams>({
url: '/order/create',
method: 'POST',
params,
});
response.code;
response.data.orderId;
response.message;import { configureGzFetch, gzFetch } from '@gz-fronted/gz-pc/fetch';
// 应用初始化入口只调用一次;微前端重新挂载时可以覆盖上一次配置。
configureGzFetch({
baseURL: '/api',
timeout: 15_000,
getToken: () => runtimeToken,
showErrorMessage: true,
});
interface ListResult {
records: Array<{ id: string }>;
total: number;
}
const result = await gzFetch<ListResult>({
url: '/users',
method: 'GET',
params: {
page: 1,
pageSize: 20,
},
});
result.records;
result.total;
interface CreateUserParams {
name: string;
}
await gzFetch<ListResult, CreateUserParams>({
url: '/users',
method: 'POST',
params: {
name: 'Alice',
},
});业务模块直接导入并调用 gzFetch,不需要自行创建请求实例。gz-pc 统一使用
配置对象式请求调用,业务代码不得混用 gzFetch(config) 与
gzFetch.get/post/put/delete 两种风格。快捷方法仅作为内部兼容层保留,不作为
业务开发规范。
如果在 configureGzFetch 之前调用 gzFetch,会明确抛出错误,不会静默使用
空配置,也不会读取 localStorage。
类型签名
gzFetch 的第一个泛型是响应数据类型,第二个泛型是请求参数类型:
function gzFetch<TResponse, TRequestParams = unknown>(
config: GzRequestConfig<TRequestParams>,
): Promise<TResponse>;成功时直接返回 response.data,因此调用结果类型是 TResponse,不是
AxiosResponse<TResponse>。
单次请求配置
| 字段 | 类型 | 必填 | 说明 |
| ------------------ | -------------------------------------- | ---- | ------------------------------------------ |
| url | string | 是 | 请求地址;配置了 baseURL 时会与其组合 |
| method | 'GET' \| 'POST' \| 'PUT' \| 'DELETE' | 是 | 请求方法,统一使用大写 |
| params | TRequestParams | 否 | 统一请求入参,由第二个泛型约束 |
| paramsInUrl | boolean | 否 | 仅 DELETE 生效;为 true 时参数放入 Query |
| headers | Record<string, string> | 否 | 本次请求附加的请求头 |
| timeout | number | 否 | 本次请求超时时间,单位毫秒;优先于实例配置 |
| showErrorMessage | boolean | 否 | 是否调用 gz-ui-web 的 message.error |
| skipAuth | boolean | 否 | 为 true 时跳过 Token 注入 |
| responseType | 'json' \| 'blob' \| 'text' | 否 | 响应数据类型,默认 json |
| signal | AbortSignal | 否 | 取消请求的标准 AbortSignal |
| withCredentials | boolean | 否 | 是否携带跨域 Cookie 等凭证 |
请求入参字段统一使用 params,业务代码不需要区分 Axios 的 params 和 data:
- GET:
params转为 URL Query。 - POST、PUT、DELETE:
params转为 Request Body。
DELETE 的 paramsInUrl 默认为 false。仅当兼容明确要求 Query 参数的 DELETE
接口时设置为 true,此时 params 只转为 URL Query,不再发送 Request Body:
await gzFetch<void, { id: number }>({
url: '/users',
method: 'DELETE',
params: { id: 1 },
paramsInUrl: true,
});业务请求配置不支持 data,也不得混用 params 和 data。
POST、PUT、DELETE 的 params 支持 JSON 对象、FormData、URLSearchParams 和 Blob;
自定义请求头通过 headers 传入。
完整示例:
interface SaveParams {
id: string;
name: string;
}
interface SaveResult {
success: boolean;
}
const controller = new AbortController();
const result = await gzFetch<SaveResult, SaveParams>({
url: '/user/save',
method: 'POST',
params: {
id: '1001',
name: '张三',
},
headers: {
'X-Request-Source': 'gz-pc',
},
timeout: 10_000,
showErrorMessage: true,
skipAuth: false,
responseType: 'json',
signal: controller.signal,
});应用初始化配置
configureGzFetch 只在应用初始化层调用。它接收以下配置:
| 字段 | 类型 | 默认值 | 说明 |
| ------------------------- | ----------------------------------------------------------- | ----------------- | --------------------------------- |
| baseURL | string | 无 | 所有业务请求的基础地址 |
| timeout | number | 15000 | 默认请求超时时间,单位毫秒 |
| validateStatus | (status: number) => boolean | 所有 2xx | 自定义 HTTP 成功状态范围 |
| getToken | () => string \| undefined \| Promise<string \| undefined> | 无 | 每次请求前动态获取 Token |
| showErrorMessage | boolean | true | 实例级错误提示开关 |
| preserveNumberPrecision | boolean | false | JSON 数值精度保护,可能返回字符串 |
| auth.headerName | string | Authorization | Token 请求头名称 |
| auth.formatToken | (token: string) => string | Bearer ${token} | Token 格式化函数 |
| unauthorized | GzFetchUnauthorizedOptions | 未开启 | 可选的 HTTP 401 统一处理 |
| middlewares | readonly GzFetchMiddleware[] | [] | 请求、响应和错误中间件 |
需要覆盖默认成功状态时,只在应用初始化层配置:
configureGzFetch({
validateStatus: (status) => status === 200 || status === 304,
});当前稳定 API 没有开放 Axios 的 paramsSerializer、onUploadProgress 等配置,也
暂不支持 PATCH。业务代码只应传入上表列出的字段;如果后续确有通用场景,再
通过 gz-pc 统一扩展类型和请求核心。
JSON 数值精度保护
交易等需要保留长 ID 或高精度数值的项目,在应用初始化时显式开启:
configureGzFetch({
baseURL: '/api',
preserveNumberPrecision: true,
});默认关闭,继续使用 Axios 的原有解析规则。开启后,在首次 JSON.parse 前处理
原始 JSON:超出安全整数范围或转为 number 会丢失十进制有效数字的数值,
保留为原始字符串;普通数字仍为 number,已有字符串不变。成功响应和 HTTP
错误响应使用相同规则,onResponse 和 GzFetchError.responseData 都能获得保精度的值。
例如原始响应 {"targtAllVarCodeSum":352782533194208303,"count":2} 会返回
{ targtAllVarCodeSum: '352782533194208303', count: 2 }。
不能在 onResponse 中对已经舍入的值调用 String() 来恢复精度。
此配置不处理请求参数,不解包响应,也不转换显式 responseType: 'text' 或
'blob' 的内容。独立服务实例同样可通过 createGzFetch 开启。可能返回字符串的
DTO 字段应声明为 string | number,接收后不要再无条件调用 Number()。
该能力保护传输和解析过程,不提供高精度算术运算。
跨域凭证
需要浏览器携带跨域 Cookie 等凭证时,在单次请求中开启 withCredentials:
await gzFetch<QueryPresetResponse, QueryPresetParams>({
url: '/queryFilterTemplate/list',
method: 'POST',
params,
withCredentials: true,
});Token
getToken 会在每次请求前动态执行。gz-fetch 不读取 Props、React Context 或
localStorage。
默认请求头为 Authorization: Bearer <token>:
configureGzFetch({
getToken: async () => tokenStore.get(),
});
await gzFetch<PublicConfig>({
url: '/public/config',
method: 'GET',
skipAuth: true,
});也可以修改 Header 名称和格式:
configureGzFetch({
getToken: () => token,
auth: {
headerName: 'X-Token',
formatToken: (value) => `Token ${value}`,
},
});错误提示
HTTP 非 2xx 时仅尝试读取响应体的 msg。没有有效 msg 时使用默认文案,最终
抛出 GzFetchError。
错误类型包括:
HTTP_ERRORNETWORK_ERRORTIMEOUT_ERRORCANCELED_ERRORUNKNOWN_ERROR
错误 Message 默认开启,优先级为“单请求配置 > 实例配置 > 默认值 true”:
configureGzFetch({ showErrorMessage: true });
await gzFetch<void, SaveParams>({
url: '/save',
method: 'POST',
params,
showErrorMessage: false,
});取消请求会转换成 CANCELED_ERROR,且默认不调用 message.error:
const controller = new AbortController();
const promise = gzFetch<UserDetail>({
url: '/users',
method: 'GET',
signal: controller.signal,
});
controller.abort();
await promise;HTTP 401 统一处理
401 统一处理默认关闭,未显式开启时继续执行原有 HTTP 错误提示和抛错逻辑,不弹窗、
不跳转。需要启用时,在应用初始化配置中传入 unauthorized.enabled: true:
configureGzFetch({
baseURL: '/api',
getToken: () => runtimeToken,
unauthorized: {
enabled: true,
loginUrl: '/login',
modalTitle: '登录失效',
modalMessage: '当前登录状态已失效,请重新登录。',
},
});配置默认值如下:
| 字段 | 类型 | 默认值 |
| ---------------- | ------------ | --------------------------------------- |
| enabled | boolean | false |
| loginUrl | string | /login |
| modalTitle | string | 登录失效 |
| modalMessage | string | 当前登录状态已失效,请重新登录。 |
| onUnauthorized | () => void | 无;确认后使用 window.location.assign |
为了让普通错误消息和 401 弹窗都继承宿主项目当前的 gz-ui-web 主题,需要在应用根部的
ConfigProvider 内挂载一次 GzFetchFeedbackProvider:
import { GzFetchFeedbackProvider } from '@gz-fronted/gz-pc/fetch';
import { ConfigProvider } from 'gz-ui-web';
<ConfigProvider themeMode={themeMode}>
<GzFetchFeedbackProvider>
<App />
</GzFetchFeedbackProvider>
</ConfigProvider>;该 Provider 使用 message.useMessage() 和受控 gz-ui-web Modal,不会在已接入的项目中
调用脱离 React 上下文的静态反馈 API。因此主题切换时,普通错误消息和 401 弹窗都会
继续读取宿主 ConfigProvider 的主题 Token,无需复制亮色或暗色样式。业务页面不需要
维护反馈状态。未挂载该 Provider 的已有项目仍会回退到原来的静态 message.error,
不会产生 breaking change。GzFetchUnauthorizedModal 继续保留为兼容 API,但新项目
优先使用统一 Provider。
所有由同一份 gz-pc 包创建的默认客户端和独立客户端共享一个 401 管理器。首个 401 打开弹窗,后续并发 401 不会创建新弹窗;取消或弹窗关闭动画结束后会释放状态,后续 独立发生的 401 可以再次打开。确认操作也有同一状态锁,不会重复执行登录跳转。
业务项目需要自定义 SSO、登出或状态清理时传入 onUnauthorized。用户确认后优先执行
该回调;只有未提供回调时才通过 window.location.assign(loginUrl) 跳转:
configureGzFetch({
unauthorized: {
enabled: true,
onUnauthorized: () => {
authService.logout();
},
},
});开启该能力后,401 仍会作为 GzFetchError 抛给调用方,但请求核心不会再为同一个
401 额外调用普通 message.error。其他 HTTP 错误、网络错误、超时和取消请求行为不变。
中间件
中间件的 onRequest、onResponse 和 onError 都按注册顺序执行,不采用洋葱
模型:
configureGzFetch({
middlewares: [
{
onRequest(config) {
return {
...config,
headers: {
...config.headers,
'X-Trace-ID': crypto.randomUUID(),
},
};
},
onResponse(data, context) {
console.debug(context.status);
return data;
},
onError(error) {
console.error(error.type);
},
},
],
});独立请求实例
普通业务模块禁止反复调用 createGzFetch。它只用于确实需要独立请求实例的高级
场景,例如多后端服务、独立 baseURL、独立 Token 或独立中间件链:
import { createGzFetch } from '@gz-fronted/gz-pc/fetch';
const reportingFetch = createGzFetch({
baseURL: '/reporting-api',
getToken: () => reportingToken,
});
const report = await reportingFetch<ReportResult>({
url: '/reports/latest',
method: 'GET',
});默认 gzFetch 和独立实例都复用同一套请求核心、错误处理、Token 注入和中间件
实现。
Blob
gz-fetch 只返回 Blob,不创建下载链接、不解析文件名:
const file = await gzFetch<Blob, ExportParams>({
url: '/export',
method: 'POST',
params,
responseType: 'blob',
});Hooks
import {
useDebounce,
useDebounceFn,
usePagination,
useRequest,
} from '@gz-fronted/gz-pc/hooks';gz-pc/hooks 通过 export * from 'ahooks' 完整透传 ahooks 的公开 API,作为团队
统一的 Hooks 使用入口。后续自定义 Hook 也从同一入口导出:
export * from 'ahooks';
export * from './use-table-height';新增自定义 Hook 时不得与 ahooks 已有导出重名。
formatDate
import { formatDate } from '@gz-fronted/gz-pc/utils';
formatDate(new Date()); // YYYY-MM-DD HH:mm:ss
formatDate(Date.now(), 'YYYY-MM-DD');支持 string | number | Date | null | undefined。null、undefined、空字符串和
无效日期统一返回 --。该入口不依赖 React、ahooks、Axios 或 gz-ui-web。
Query 参数
import { objectToQuery, queryToObject } from '@gz-fronted/gz-pc/utils';
queryToObject('https://example.com/list?page=1&tag=a&tag=b');
// { page: '1', tag: ['a', 'b'] }
objectToQuery({
page: 1,
keyword: 'audit log',
tag: ['a', 'b'],
});
// page=1&keyword=audit+log&tag=a&tag=bqueryToObject 支持完整 URL、带 ? 的 query 和纯 query 字符串;重复参数返回
字符串数组。objectToQuery 返回不带前导 ? 的字符串,跳过 undefined,
将 null 转为空值,并将数组序列化为重复参数。
MSW Mock
完整接入样例位于 examples/msw。核心启动顺序如下:
async function bootstrap(): Promise<void> {
if (import.meta.env.VITE_USE_MOCK === 'true') {
const { startMock } = await import('./mock/browser');
await startMock();
}
renderApp();
}
void bootstrap();worker.start({
onUnhandledRequest: 'bypass',
});必须等待 worker.start() 完成后再渲染应用,避免首次请求漏拦截。具体 Handler
和 Mock 数据由业务项目维护,gz-pc 不提供业务 Mock。
本地接入
先在本仓库构建并生成 tarball:
npm run build
npm pack然后在业务项目安装生成的 .tgz:
npm install ../gz-pc/gz-fronted-gz-pc-0.1.0.tgz也可以在联调期间使用业务项目支持的 workspace 或本地 file: 依赖。发布前建议
优先用 tarball 验证,因为它与 npm 实际安装内容最接近。
提交规范
项目使用 Husky、lint-staged、Commitlint 和 Conventional Commits。安装依赖时
prepare 脚本会初始化 Husky。
pre-commit 在提交前只检查 Git 暂存区中的文件:
- JavaScript、JSX、TypeScript、TSX:依次执行
eslint --fix和prettier --write - JSON、Markdown、YAML、CSS、Less:执行
prettier --write
可以自动修复的问题会写回暂存文件;仍存在的 ESLint 错误会阻止提交。lint-staged
只负责提交前的快速增量检查,不替代完整的 lint、typecheck、test 和
build。
Prettier 对 JavaScript、TypeScript 和 JSX 统一使用单引号;JSON 按标准继续使用 双引号。
commit-msg 使用 Commitlint 校验提交信息:
feat: add request client
fix: handle canceled request缺少合法 type、subject 或其他不符合 Conventional Commits 的提交信息会被拒绝。
第一版边界
第一版不包含 Token 自动刷新、自动重试、缓存、请求去重、并发控制、业务 code
判断、data 解包、分页/日期转换、错误上报、自动文件下载、内置业务 Mock、
React Context、Zustand 或复杂洋葱中间件。
