关于 single-or-array-pattern
一种 TypeScript API 设计模式指南,建议单项和数组输入在函数入口统一规范化,再由同一逻辑处理。适用于 CRUD、批处理和接收依赖项的工厂函数。
主要功能
- 01接受单项或数组输入
- 02在入口统一规范化参数
- 03用同一逻辑处理所有项
- 04涵盖 CRUD 与批处理示例
- 05列出适用场景和反模式
适用场景
- 01设计支持单条与批量操作的 API
- 02为工厂函数接收单个或多个依赖
- 03统一实现记录的创建或删除
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ epicenterhq/ epicenter --skill single-or-array-pattern安装后在 Claude Code 中输入/single-or-array-pattern确认已加载。
详细介绍
单项或数组模式
同时接受单项和数组,在顶部统一规范化,然后以统一方式处理。
快速参考
function create(itemOrItems: T | T[]): Promise<Result<void, E>> {
const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
// ... implementation works on items array
}
结构
- 接受
T | T[]作为参数类型 - 在函数顶部使用
Array.isArray()规范化 - 所有逻辑都以数组为基础:只使用一条代码路径
function createServer(clientOrClients: Client | Client[], options?: Options) {
const clients = Array.isArray(clientOrClients)
? clientOrClients
: [clientOrClients];
// All real logic here, working with the array
for (const client of clients) {
// ...
}
}
命名约定
| 参数 | 规范化后的变量 |
|---|---|
recordingOrRecordings | recordings |
clientOrClients | clients |
itemOrItems | items |
paramsOrParamsArray | paramsArray |
适用场景
适合:
- CRUD 操作(创建、更新、删除)
- 批处理 API
- 接受依赖项的工厂函数
- 任何“对一个或多个对象执行某操作”的场景
不适合:
- 单项和批量操作语义不同
- 返回类型差异很大
- 数组版本需要不同的选项
代码库示例
服务端工厂(packages/server/src/server.ts)
function createServer(
clientOrClients: AnyWorkspaceClient | AnyWorkspaceClient[],
options?: ServerOptions,
) {
const clients = Array.isArray(clientOrClients)
? clientOrClients
: [clientOrClients];
// All server setup logic directly here
const workspaces: Record<string, AnyWorkspaceClient> = {};
for (const client of clients) {
workspaces[client.id] = client;
}
// ...
}
服务方法
delete: async (recordingOrRecordings) => {
const recordings = Array.isArray(recordingOrRecordings)
? recordingOrRecordings
: [recordingOrRecordings];
const ids = recordings.map((r) => r.id);
return tryAsync({
try: () => store.bulkDelete(ids),
catch: (error) => RecordingStoreError.MutationFailed({ cause: error }),
});
},
RPC 修改操作
export const recordingKeys = defineKeys({
delete: ['recordings', 'delete'],
});
type DeleteRecordingsInput = Recording | Recording[];
export const recordings = {
delete: defineMutation({
mutationKey: recordingKeys.delete,
mutationFn: async (recordings: DeleteRecordingsInput) => {
const recordingsArray = Array.isArray(recordings)
? recordings
: [recordings];
for (const recording of recordingsArray) {
services.blobs.audio.revokePlaybackUrl(recording.id);
}
const { error } = await services.recordings.delete(recordingsArray);
if (error) return Err(error);
return Ok(undefined);
},
}),
};
反模式
不要:为单项和数组分别编写函数
// Harder to maintain, users must remember two APIs
function createRecording(recording: Recording): Promise<...>;
function createRecordings(recordings: Recording[]): Promise<...>;
不要:强制所有情况都使用数组
// Awkward for single items
createRecordings([recording]); // Ugly
不要:在重载中重复逻辑
// BAD: Logic duplicated
function create(item: T) {
return db.insert(item); // Duplicated
}
function create(items: T[]) {
return db.bulkInsert(items); // Different code path
}
// GOOD: Single implementation
function create(itemOrItems: T | T[]) {
const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
return db.bulkInsert(items); // One code path
}
参考资料
- 完整文章:包含更多示例的详细说明
评论
支持 Markdown