Skip to content
On this page

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;