refactor(data): replace schema classes with readonly models
This commit is contained in:
@@ -12,7 +12,7 @@ description: Sync this Next.js frontend with backend API documentation
|
||||
当后端 API 新增、删除、字段结构调整、响应语义变化时,使用本 skill 更新:
|
||||
|
||||
- 网络层
|
||||
- schema / 不可变数据类
|
||||
- schema / 不可变纯数据
|
||||
- repository
|
||||
- storage
|
||||
- XState store
|
||||
@@ -22,18 +22,18 @@ description: Sync this Next.js frontend with backend API documentation
|
||||
|
||||
## 当前项目结构
|
||||
|
||||
| 层级 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| API Path | `src/data/services/api/api_path.ts` | 统一维护接口路径 |
|
||||
| API Service | `src/data/services/api/*_api.ts` | 调用 `httpClient` 并解析响应 |
|
||||
| Schema Model | `src/data/schemas/<module>/*.ts` | Zod schema 与不可变数据类,负责解析和序列化 |
|
||||
| Repository Interface | `src/data/repositories/interfaces/i*_repository.ts` | 仓库接口 |
|
||||
| Repository | `src/data/repositories/*_repository.ts` | 业务仓库实现 |
|
||||
| Storage | `src/data/storage/*` | 本地持久化 |
|
||||
| Store | `src/stores/<module>/*` | XState 状态机、actors、helpers、sync |
|
||||
| UI | `src/app/**` | Next.js app router UI |
|
||||
| Mock | `src/data/mock/<module>/**` | 模拟请求 / 响应数据 |
|
||||
| Tests | `**/__tests__/*.test.ts` | schema model、helper、machine transition 测试 |
|
||||
| 层级 | 路径 | 说明 |
|
||||
| -------------------- | --------------------------------------------------- | ----------------------------------------------- |
|
||||
| API Path | `src/data/services/api/api_path.ts` | 统一维护接口路径 |
|
||||
| API Service | `src/data/services/api/*_api.ts` | 调用 `httpClient` 并解析响应 |
|
||||
| Schema Model | `src/data/schemas/<module>/*.ts` | Zod schema 与不可变纯数据类型,负责解析和归一化 |
|
||||
| Repository Interface | `src/data/repositories/interfaces/i*_repository.ts` | 仓库接口 |
|
||||
| Repository | `src/data/repositories/*_repository.ts` | 业务仓库实现 |
|
||||
| Storage | `src/data/storage/*` | 本地持久化 |
|
||||
| Store | `src/stores/<module>/*` | XState 状态机、actors、helpers、sync |
|
||||
| UI | `src/app/**` | Next.js app router UI |
|
||||
| Mock | `src/data/mock/<module>/**` | 模拟请求 / 响应数据 |
|
||||
| Tests | `**/__tests__/*.test.ts` | schema model、helper、machine transition 测试 |
|
||||
|
||||
## 后端文档来源
|
||||
|
||||
@@ -84,13 +84,16 @@ git status --short
|
||||
否则 schema 应尽量防御性解析,例如:
|
||||
|
||||
```ts
|
||||
z.string().nullable().transform((v) => v ?? "").default("")
|
||||
z.string()
|
||||
.nullable()
|
||||
.transform((v) => v ?? "")
|
||||
.default("");
|
||||
```
|
||||
|
||||
项目已有通用 helper:
|
||||
|
||||
```ts
|
||||
src/data/schemas/nullable-defaults.ts
|
||||
src / data / schemas / nullable - defaults.ts;
|
||||
```
|
||||
|
||||
### 3. 更新 API Path
|
||||
@@ -98,7 +101,7 @@ src/data/schemas/nullable-defaults.ts
|
||||
新增或修改:
|
||||
|
||||
```ts
|
||||
src/data/services/api/api_path.ts
|
||||
src / data / services / api / api_path.ts;
|
||||
```
|
||||
|
||||
示例:
|
||||
@@ -122,46 +125,32 @@ src/data/schemas/<module>/*.ts
|
||||
- 输入类型用 `z.input<typeof Schema>`。
|
||||
- 对后端可能返回 `null` 的字段做防御性处理。
|
||||
- 不要在 schema 中写 UI 逻辑。
|
||||
- Schema 与对应不可变数据类必须定义在同一个文件中。
|
||||
- 数据类的 `declare readonly` 只声明业务代码直接访问的字段。
|
||||
- Schema 输出必须是纯数据,不要定义 class 或工厂对象。
|
||||
- 对象 Schema 使用 `.readonly()`;已知嵌套对象、数组和 Record 也必须逐层 readonly。
|
||||
- transform 应先完成归一化,再在最终输出应用 `.readonly()`。
|
||||
- 容器默认值必须经过 Schema 解析;使用 `prefault` 或在最外层应用 readonly,不能让 `.default()` 返回未冻结对象。
|
||||
- 后端返回但前端既不读取、也不透传的字段,不要加入 Schema。
|
||||
- 仅用于请求序列化、响应兼容或内部透传的字段保留在 Schema 中,不声明 class 属性。
|
||||
- 数据类统一使用 `from()`、`fromJson()`、`toJson()` 与 `Object.freeze(this)`。
|
||||
- 没有嵌套转换或自定义方法时,使用 `src/data/schemas/schema_model.ts` 的 `createSchemaModel()`。
|
||||
- 仅用于请求序列化、响应兼容或内部透传的字段直接保留在 Schema 中。
|
||||
- 统一使用 `XxxSchema.parse()` 构造请求与解析响应,不增加 `from()`、`fromJson()` 或 `toJson()` 包装。
|
||||
- 不要再创建独立的数据传输对象层。
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
export const XxxResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
count: z.number().default(0),
|
||||
});
|
||||
export const XxxResponseSchema = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
tags: z
|
||||
.array(z.string())
|
||||
.default(() => [])
|
||||
.readonly(),
|
||||
count: z.number().default(0),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export type XxxResponseInput = z.input<typeof XxxResponseSchema>;
|
||||
export type XxxResponseData = z.output<typeof XxxResponseSchema>;
|
||||
|
||||
export class XxxResponse {
|
||||
declare readonly id: string;
|
||||
|
||||
private constructor(input: XxxResponseInput) {
|
||||
const data = XxxResponseSchema.parse(input);
|
||||
Object.assign(this, data);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
static from(input: XxxResponseInput): XxxResponse {
|
||||
return new XxxResponse(input);
|
||||
}
|
||||
|
||||
static fromJson(json: unknown): XxxResponse {
|
||||
return XxxResponse.from(json as XxxResponseInput);
|
||||
}
|
||||
|
||||
toJson(): XxxResponseData {
|
||||
return XxxResponseSchema.parse(this);
|
||||
}
|
||||
}
|
||||
export type XxxResponse = XxxResponseData;
|
||||
```
|
||||
|
||||
### 5. 更新 API Service
|
||||
@@ -177,9 +166,9 @@ src/data/services/api/<module>_api.ts
|
||||
```ts
|
||||
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.xxx, {
|
||||
method: "POST",
|
||||
body: request.toJson(),
|
||||
body: request,
|
||||
});
|
||||
return XxxResponse.fromJson(unwrap(env) as Record<string, unknown>);
|
||||
return XxxResponseSchema.parse(unwrap(env));
|
||||
```
|
||||
|
||||
注意:
|
||||
@@ -207,7 +196,7 @@ async getXxx(): Promise<Result<XxxResponse>> {
|
||||
|
||||
Repository 可以做轻量编排,例如:
|
||||
|
||||
- request 数据类构造
|
||||
- request schema 解析
|
||||
- 多接口组合
|
||||
- 本地 storage 同步
|
||||
- 本地模型与接口模型转换
|
||||
@@ -333,7 +322,7 @@ pnpm exec vitest run <related tests>
|
||||
|
||||
1. 文档确认 path / method / payload。
|
||||
2. `api_path.ts` 新增路径。
|
||||
3. 在同一文件新增 schema 与不可变数据类。
|
||||
3. 在同一文件新增 readonly schema 与输入/输出类型。
|
||||
4. API service 新增方法。
|
||||
5. repository interface + implementation 新增方法。
|
||||
6. 如需要,接入 store actor / event。
|
||||
@@ -343,7 +332,7 @@ pnpm exec vitest run <related tests>
|
||||
### 修改响应字段
|
||||
|
||||
1. 更新 schema。
|
||||
2. 更新同文件数据类的公开字段和 `toJson`。
|
||||
2. 更新同文件输入/输出类型及相关 parse 调用。
|
||||
3. 更新 mapper/helper。
|
||||
4. 更新 UI 或状态机依赖字段。
|
||||
5. 更新 mock 和测试。
|
||||
|
||||
Reference in New Issue
Block a user