defu
德芙,纵享丝滑
轻量快速地递归分配默认属性。
用法
ts
import { defu } from 'defu'
//* 左边优先级更高
console.log(defu({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }))
//* { a: { b: 2, c: 3 } }
特性
- 支持自定义合并ts
import { createDefu } from 'defu' const ext = createDefu((obj, key, value) => { if (typeof obj[key] === 'number' && typeof value === 'number') { obj[key] += value return true } }) ext({ cost: 15 }, { cost: 10 }) //* { cost: 25 }
- 支持函数合并ts
import { defuFn } from 'defu' defuFn({ ignore: (val) => val.filter(item => item !== 'dist'), count: (count) => count + 20 }, { ignore: ['node_modules','dist'], count: 10 }) //* { ignore: ['node_modules'], count: 30 }
- 支持数组函数合并ts
import { defuArrayFn } from 'defu' defuArrayFn({ ignore(val) => val.filter(i => i !== 'dist'), count: () => 20 }, { ignore: [ 'node_modules', 'dist' ], count: 10 }) //* { ignore: ['node_modules'], count: () => 20 }
源码
ts
import type { Merger, DefuFn as DefuFunction, DefuInstance } from "./types";
function isObject(value: any) {
return value !== null && typeof value === "object";
}
//* 把baseObject的值合并到defaults上
function _defu<T>(
baseObject: T,
defaults: any,
namespace = ".",
merger?: Merger
): T {
//* 默认值不是对象,则当作空对象处理
if (!isObject(defaults)) {
return _defu(baseObject, {}, namespace, merger);
}
const object = Object.assign({}, defaults);
for (const key in baseObject) {
//* 忽略原型和构造函数
if (key === "__proto__" || key === "constructor") {
continue;
}
const value = baseObject[key]; //* 新值
//* 忽略空值
if (value === null || value === undefined) {
continue;
}
//* 自定义合并器,结果已作用到object上,自定义合并器必须返回true才能continue
if (merger && merger(object, key, value, namespace)) {
continue;
}
//* 都是数组,则合并
if (Array.isArray(value) && Array.isArray(object[key])) {
//* 顺序为,新值在前
object[key] = [...value, ...object[key]];
} else if (isObject(value) && isObject(object[key])) {
//* 都是对象,递归进行合并
object[key] = _defu(
value,
object[key],
//* 下一层的key
(namespace ? `${namespace}.` : "") + key.toString(),
merger
);
} else {
//* 普通值,直接覆盖
object[key] = value;
}
}
return object;
}
//* 工厂函数,可自定义合并器,生成defu函数
export function createDefu(merger?: Merger): DefuFunction {
return (...arguments_) =>
//* 从左到右,且左边优先级高
arguments_.reduce((p, c) => _defu(p, c, "", merger), {} as any);
}
//* 标准版本
export const defu = createDefu() as DefuInstance;
export default defu;
//* 自定义合并器支持函数
export const defuFn = createDefu((object, key, currentValue) => {
if (
typeof object[key] !== "undefined" &&
typeof currentValue === "function"
) {
object[key] = currentValue(object[key]);
return true;
}
});
//* 自定义合并器支持数组
export const defuArrayFn = createDefu((object, key, currentValue) => {
if (Array.isArray(object[key]) && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
export type { Defu } from "./types";
ts
export type Input = Record<string | number | symbol, any>;
export type IgnoredInput =
| boolean
| number
| null
| any[]
| Record<never, any>
| undefined;
//* 合并器
export type Merger = <T extends Input, K extends keyof T>(
//* 默认对象
object: T,
//* 对应的key
key: keyof T,
//* key对应的默认value
value: T[K],
//* 递归合并时的前缀
namespace: string
) => any;
//* 无效值的类型
type nullish = null | undefined | void;
export type MergeObjects<
Destination extends Input,
Defaults extends Input
> = Destination extends Defaults
? Destination //* 如果目标继承自默认,则合并后结果就是目标
//* 从目标中排除相同key,目标中特有的key
: Omit<Destination, keyof Destination & keyof Defaults> &
//* 从默认中排除相同key,默认中特有的key
Omit<Defaults, keyof Destination & keyof Defaults> & {
-readonly [Key in keyof Destination & //* 所有相同的只读key
keyof Defaults]: Destination[Key] extends nullish
? Defaults[Key] extends nullish //* 目标值无效,则判断默认值是否有效
? nullish //* 默认值也无效,则使用默认值
: Defaults[Key] //* 默认值有效,则使用默认值
: Defaults[Key] extends nullish //* 目标值有效,判断默认值是否有效
? Destination[Key] //* 默认值无效,则使用目标值
: Merge<Destination[Key], Defaults[Key]>; //* 默认值有效,则进行合并
};
//* 运用了递归、继承、infer推断等,十分精彩
export type Defu<
S extends Input,
D extends Array<Input | IgnoredInput>
> = D extends [infer F, ...infer Rest]
? F extends Input //* 第一项是对象
? Rest extends Array<Input | IgnoredInput> //* 剩余是有效数组
? Defu<MergeObjects<S, F>, Rest> //* 合并2项结果后再继续递归合并
: MergeObjects<S, F> //* 直接合并得到结果
: F extends IgnoredInput //* 第一项不是对象
? Rest extends Array<Input | IgnoredInput> //* 剩余是有效数组
? Defu<S, Rest> //* 直接合并
: S //* 直接使用目标值
: S //* 直接使用目标值
: S; //* 直接使用目标值
export type DefuFn = <
Source extends Input,
Defaults extends Array<Input | IgnoredInput>
>(
source: Source,
...defaults: Defaults
) => Defu<Source, Defaults>;
export interface DefuInstance {
<Source extends Input, Defaults extends Array<Input | IgnoredInput>>(
source: Source | IgnoredInput,
...defaults: Defaults
): Defu<Source, Defaults>;
fn: DefuFn;
arrayFn: DefuFn;
extend(merger?: Merger): DefuFn;
}
export type MergeArrays<Destination, Source> = Destination extends Array<
infer DestinationType
>
? Source extends Array<infer SourceType>
? Array<DestinationType | SourceType>
: Source | Array<DestinationType>
: Source | Destination;
export type Merge<Destination extends Input, Defaults extends Input> =
//* Remove explicitly null types
Destination extends nullish
? Defaults extends nullish
? nullish
: Defaults
: Defaults extends nullish
? Destination
: //* Handle arrays
Destination extends Array<any>
? Defaults extends Array<any>
? MergeArrays<Destination, Defaults>
: Destination | Defaults
: //* Don't attempt to merge Functions, RegExps, Promises
Destination extends Function
? Destination | Defaults
: Destination extends RegExp
? Destination | Defaults
: Destination extends Promise<any>
? Destination | Defaults
: //* Don't attempt to merge Functions, RegExps, Promises
Defaults extends Function
? Destination | Defaults
: Defaults extends RegExp
? Destination | Defaults
: Defaults extends Promise<any>
? Destination | Defaults
: //* Ensure we only merge Records
Destination extends Input
? Defaults extends Input
? MergeObjects<Destination, Defaults>
: Destination | Defaults
: Destination | Defaults;