Skip to content

定义字典,增强代码可读性

沈孟平
最后更新于 2026-04-23 19:53:53

defineDict 适合把“数组配置”快速转成“按 key 访问的字典”,既减少手写循环,也能保留不错的类型推断。

能解决什么问题

  • 从配置数组中提取某一列值
  • 把枚举配置数组转成字典映射
  • 把多列字段按主键聚合成对象

三种常见用法

1. 提取一列值

ts
const ids = defineDict(
  [{ id: 1, name: 'A' }, { id: 2, name: 'B' }],
  'id',
)
// [1, 2]

2. 创建单值字典

ts
const nameMap = defineDict(
  [{ id: 1, name: 'A' }, { id: 2, name: 'B' }],
  'id',
  'name',
)
// { 1: 'A', 2: 'B' }

3. 创建多值字典

ts
const infoMap = defineDict(
  [{ id: 1, name: 'A', age: 20 }, { id: 2, name: 'B', age: 30 }],
  'id',
  ['name', 'age'],
)
// { 1: { name: 'A', age: 20 }, 2: { name: 'B', age: 30 } }

源码签名

ts
const toRawType = (val: unknown) => Object.prototype.toString.call(val).slice(8, -1)

const isPropertyKey = (val: unknown): val is PropertyKey => ['String', 'Number', 'Symbol'].includes(toRawType(val))

function pick<T extends object>(target: T, keys: (keyof T)[]) {
  return keys.reduce((dict, key) => ({ ...dict, [key]: target[key] }), {})
}

type ValidKeys<T, K extends keyof T = keyof T> = K extends K ? (T[K] extends PropertyKey ? K : never) : never

/**
 * 将对象数组转换为字典或提取特定键的值数组
 *
 * @description
 * 该函数有三种重载:
 * 1. 提取对象数组中指定键的值数组
 * 2. 创建以指定键为键,单个值为值的字典
 * 3. 创建以指定键为键,多个值组成对象为值的字典
 *
 * @example
 * // 用法1: 提取值数组
 * defineDict([{id: 1, name: 'a'}, {id: 2, name: 'b'}], 'id')
 * // 返回 [1, 2]
 *
 * // 用法2: 创建单值字典
 * defineDict([{id: 1, name: 'a'}, {id: 2, name: 'b'}], 'id', 'name')
 * // 返回 { 1: 'a', 2: 'b' }
 *
 * // 用法3: 创建多值字典
 * defineDict([{id: 1, name: 'a', age: 20}, {id: 2, name: 'b', age: 30}], 'id', ['name', 'age'])
 * // 返回 { 1: {name: 'a', age: 20}, 2: {name: 'b', age: 30} }
 *
 * @param defs - 源对象数组
 * @param key - 用作键的属性名
 * @param values - 可选,用作值的属性名(单个字符串)或属性名数组
 * @returns 根据参数返回值数组或字典对象
 */
function defineDict<T extends object, K extends keyof T>(defs: T[], key: K): T[K][]
function defineDict<T extends object, K extends ValidKeys<T>, V extends keyof T>(
  defs: T[],
  key: K,
  values: V
): Record<T[K] extends PropertyKey ? T[K] : never, T[V]>
function defineDict<T extends object, K extends ValidKeys<T>, V extends keyof T>(
  defs: T[],
  key: K,
  values: V[]
): Record<T[K] extends PropertyKey ? T[K] : never, Pick<T, V>>
function defineDict<T extends object, K extends keyof T, V extends keyof T>(defs: T[], key: K, values?: V | V[]) {
  if (typeof values === 'undefined') {
    return defs.map(def => def[key])
  }

  return defs.reduce((map, def) => {
    const _key = def[key]
    if (!isPropertyKey(_key))
      return map

    const _val = Array.isArray(values) ? pick(def, values) : def[values]

    return { ...map, [_key]: _val }
  }, {})
}

export { defineDict }

使用建议

  • key 最好选择稳定且可作为对象键的字段,例如 idcodevalue
  • 如果你只需要一列数组,直接传两个参数即可,不必再手写 map
  • 如果你需要高频查表,这种“数组转字典”的写法会比每次 find 更直接。

Last updated: