示例说明
PATCH 更新接口的难点是类型表达:顶层 Partial 只允许一层可选,嵌套对象的局部更新就要整体提供。DeepPartial 递归地把每一层都变可选,patch 参数被精确约束为任意深度的局部形态;合并函数返回完整类型,调用方拿到的配置保证所有必填字段都在——类型系统替你核对了完整性。
技术要点
- DeepPartial 递归可选化嵌套结构
- 数组的对象语义不参与深度合并
- 返回类型保持完整而非部分
代码解读
mergeConfig 遍历补丁键,当补丁值与基值都是普通对象时递归合并,否则直接覆盖;展开运算符先复制基线保证原对象不被修改。示例补丁只改端口与暗色模式,数据库配置原样透传,输出类型仍是 ServerConfig。
使用建议
undefined 与缺失键在合并语义上要明确区分,当前实现按覆盖处理。深度合并遇到 Map、Set 等结构时应按业务定制。热更新配置时可结合变更监听做增量生效。
代码示例
import type { DeepPartial } from 'type-fest';
interface ServerConfig {
http: { port: number; bodyLimitMb: number };
db: { url: string; poolSize: number };
features: { auditLog: boolean; darkMode: boolean };
}
function mergeConfig<T extends object>(base: T, patch: DeepPartial<T>): T {
const result: Record<string, unknown> = { ...base };
for (const [key, patchValue] of Object.entries(patch)) {
const baseValue = (base as Record<string, unknown>)[key];
const bothObjects =
patchValue && typeof patchValue === 'object' && !Array.isArray(patchValue)
&& baseValue && typeof baseValue === 'object' && !Array.isArray(baseValue);
result[key] = bothObjects
? mergeConfig(baseValue, patchValue)
: patchValue;
}
return result as T;
}
const defaults: ServerConfig = {
http: { port: 3000, bodyLimitMb: 1 },
db: { url: 'postgres://localhost/app', poolSize: 10 },
features: { auditLog: true, darkMode: false },
};
const merged = mergeConfig(defaults, { http: { port: 8080 }, features: { darkMode: true } });
console.log(merged.http.port, merged.db.poolSize, merged.features.darkMode);