TypeScript 实现的 `safeAppend` 工具函数,包含完整的类型定义和错误处理
以下是 TypeScript 实现的 `safeAppend` 工具函数,包含完整的类型定义和错误处理:
```typescript
// utils/formDataHelper.ts
import type { BlobPart } from "buffer";
/**
* 安全地将键值对添加到 FormData 对象中
*
* @param formData - 目标 FormData 对象
* @param key - 键名
* @param value - 值,支持多种类型
* @param filename - 可选文件名(仅对 Blob/File 有效)
*/
export function safeAppend(
formData: FormData,
key: string,
value: unknown,
filename?: string
): void {
// 处理 null/undefined 值
if (value === null || value === undefined) {
return;
}
// 处理布尔值
if (typeof value === "boolean") {
formData.append(key, value ? "1" : "0");
return;
}
// 处理数字
if (typeof value === "number") {
formData.append(key, value.toString());
return;
}
// 处理 File 对象
if (value instanceof File) {
formData.append(key, value, filename || value.name);
return;
}
// 处理 Blob 对象
if (value instanceof Blob) {
formData.append(key, value, filename || "blob");
return;
}
// 处理 Date 对象
if (value instanceof Date) {
formData.append(key, value.toISOString());
return;
}
// 处理数组和对象(转换为 JSON 字符串)
if (typeof value === "object") {
try {
formData.append(key, JSON.stringify(value));
return;
} catch (e) {
console.error(`无法序列化对象: ${key}`, e);
formData.append(key, "[object]");
return;
}
}
// 处理其他类型(转换为字符串)
formData.append(key, String(value));
}
/**
* 安全地将多个键值对添加到 FormData 对象中
*
* @param formData - 目标 FormData 对象
* @param data - 键值对对象
*/
export function safeAppendAll(
formData: FormData,
data: Record<string, unknown>
): void {
Object.entries(data).forEach(([key, value]) => {
safeAppend(formData, key, value);
});
}
```
### 类型定义文件 (确保类型安全)
```typescript
// types/formData.d.ts
declare module "@/utils/formDataHelper" {
export function safeAppend(
formData: FormData,
key: string,
value: unknown,
filename?: string
): void;
export function safeAppendAll(
formData: FormData,
data: Record<string, unknown>
): void;
}
```
### 使用示例
```typescript
// 在 Vue 组件中使用
import { defineComponent, ref } from "vue";
import { safeAppend, safeAppendAll } from "@/utils/formDataHelper";
export default defineComponent({
setup() {
const formData = new FormData();
const avatarFile = ref<File | null>(null);
// 添加单个值
safeAppend(formData, "nickname", "沙僧");
safeAppend(formData, "optimize_avatar", false); // 布尔值
safeAppend(formData, "priority", 1); // 数字
safeAppend(formData, "timestamp", new Date()); // Date 对象
// 添加文件
if (avatarFile.value) {
safeAppend(formData, "avatar", avatarFile.value);
}
// 添加复杂对象
const preferences = {
darkMode: true,
fontSize: 14,
notifications: {
email: true,
push: false
}
};
safeAppend(formData, "preferences", preferences);
// 或者批量添加
const allData = {
nickname: "沙僧",
timezone: "Asia/Shanghai",
optimize_avatar: false,
avatar: avatarFile.value,
metadata: { source: "web", version: 3 }
};
safeAppendAll(formData, allData);
// 发送请求
axios.post("/api/profile", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
});
}
});
```
### 企业级增强功能
1. **深度对象处理**:
```typescript
// 递归处理嵌套对象
function appendNestedObject(
formData: FormData,
key: string,
value: unknown,
parentKey = ""
): void {
const fullKey = parentKey ? `${parentKey}[${key}]` : key;
if (value === null || value === undefined) return;
if (typeof value === "object" && !(value instanceof Blob)) {
Object.entries(value).forEach(([subKey, subValue]) => {
appendNestedObject(formData, subKey, subValue, fullKey);
});
} else {
safeAppend(formData, fullKey, value);
}
}
```
2. **类型安全验证**:
```typescript
// 创建类型安全的表单构建器
interface FormField<T> {
key: string;
value: T;
required?: boolean;
}
function buildFormData(fields: FormField<unknown>[]): FormData {
const formData = new FormData();
fields.forEach(field => {
if (field.required && (field.value === null || field.value === undefined)) {
throw new Error(`缺少必填字段: ${field.key}`);
}
safeAppend(formData, field.key, field.value);
});
return formData;
}
// 使用示例
const formData = buildFormData([
{ key: "username", value: "沙僧", required: true },
{ key: "optimize_avatar", value: false },
{ key: "avatar", value: avatarFile.value }
]);
```
3. **文件处理增强**:
```typescript
// 添加文件验证
function safeAppendFile(
formData: FormData,
key: string,
file: File | Blob,
options: {
maxSize?: number; // 最大字节数
allowedTypes?: string[]; // 允许的 MIME 类型
filename?: string;
} = {}
): void {
// 文件大小验证
if (options.maxSize && file.size > options.maxSize) {
throw new Error(`文件大小超过限制: ${file.size} > ${options.maxSize}`);
}
// 文件类型验证
if (
options.allowedTypes &&
file instanceof File &&
!options.allowedTypes.includes(file.type)
) {
throw new Error(`不支持的文件类型: ${file.type}`);
}
safeAppend(formData, key, file, options.filename);
}
// 使用示例
safeAppendFile(formData, "avatar", avatarFile.value, {
maxSize: 5 * 1024 * 1024, // 5MB
allowedTypes: ["image/jpeg", "image/png", "image/webp"],
filename: "user-avatar.webp"
});
```
### 主要优势
1. **完整的类型安全**:
- 使用 TypeScript 泛型和类型守卫
- 明确的输入/输出类型定义
2. **全面支持的数据类型**:
- 原始类型:boolean, number, string
- 特殊对象:File, Blob, Date
- 复杂结构:Array, Object (自动序列化为 JSON)
3. **企业级功能**:
- 文件验证(大小、类型)
- 嵌套对象支持
- 批量操作接口
- 错误处理和日志
4. **兼容性处理**:
- 自动处理布尔值转换为 "1"/"0"
- Date 对象转换为 ISO 字符串
- 安全处理循环引用对象
这个工具函数完全解决了 Vue/TypeScript 环境下 FormData 的类型安全问题,同时提供了企业级应用所需的高级功能和安全性保障。