Appearance
keyBy
将数组转为以某字段为 key 的对象(常用于快速查找)。
- 支持字符串字段(包含点路径如
user.id)或选择器函数。 - 支持重复键保留策略:
first(保留首次出现)/last(保留最后一次,默认)。 - 支持
symbol作为键;当键为null/undefined时可选择跳过。
安装与导入
- 子路径导入:
import keyBy from '@cuixingjian/cui-utils/keyBy' - 命名导入:
import { keyBy } from '@cuixingjian/cui-utils'
使用示例
ts
import { keyBy } from '@cuixingjian/cui-utils'
// 基于简单字段
const users = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
]
const byId = keyBy(users, 'id')
// => { 1: { id: 1, name: 'A' }, 2: { id: 2, name: 'B' } }
// 点路径字段
const data = [
{ user: { id: 'u1' }, role: 'admin' },
{ user: { id: 'u2' }, role: 'user' },
]
const byUserId = keyBy(data, 'user.id')
// => { u1: { user: { id: 'u1' }, role: 'admin' }, u2: { ... } }
// 使用选择器函数(返回 string/number/symbol)
const orders = [
{ no: '001', items: 3 },
{ no: '002', items: 5 },
]
const byNo = keyBy(orders, o => o.no)
// 处理重复键:保留首次出现
const dup = [
{ id: 1, name: 'first' },
{ id: 1, name: 'last' },
]
const firstOnly = keyBy(dup, 'id', { keep: 'first' })
// => { 1: { id: 1, name: 'first' } }
// 跳过空键(null/undefined)
const arr = [
{ id: 1 },
{ id: null },
{ id: undefined },
]
const res = keyBy(arr, 'id', { skipEmptyKey: true })
// => { 1: { id: 1 } }API
函数签名
ts
function keyBy<T>(
list: T[],
selector: string | ((item: T) => PropertyKey),
options?: KeyByOptions
): Record<PropertyKey, T>参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
list | T[] | 是 | 源数组 |
selector | `string | (item: T) => PropertyKey` | 是 |
options | KeyByOptions | 否 | 选项配置,见下表。 |
KeyByOptions
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
keep | `'first' | 'last'` | 'last' |
skipEmptyKey | boolean | true | 当生成的键为 null/undefined 时跳过该项。若设为 false,将以字符串 'undefined' 作为键进行归集。 |
返回值
| 类型 | 说明 |
|---|---|
Record<PropertyKey, T> | 键为 string/number/symbol 的对象映射,值为数组项。 |
注意事项
- 当
selector返回非string/number/symbol类型时会自动转为字符串作为键。 - 当
skipEmptyKey为false且键为undefined时,使用字符串'undefined'作为键。 - 使用函数选择器可直接返回
symbol作为键,便于与外部Symbol常量映射。
源码
ts
import keyBy from '@cuixingjian/cui-utils/keyBy'展开查看源码
ts
export type KeyBySelector<T> = (item: T) => PropertyKey
export interface KeyByOptions {
keep?: 'first' | 'last'
skipEmptyKey?: boolean
}
function getByPath(obj: any, path: string): unknown {
const parts = path.split('.')
let cur = obj
for (const p of parts) {
if (cur == null) return undefined
cur = cur[p]
}
return cur
}
export function keyBy<T extends Record<string | number | symbol, any>>(
list: T[],
selector: string | KeyBySelector<T>,
options: KeyByOptions = {}
): Record<PropertyKey, T> {
const { keep = 'last', skipEmptyKey = true } = options
const out: Record<PropertyKey, T> = {}
const makeKey = (item: T): PropertyKey | undefined => {
if (typeof selector === 'function') {
const k = selector(item)
return k as PropertyKey
}
const val = getByPath(item, selector)
if (val === null || val === undefined) return undefined
const t = typeof val
if (t === 'string' || t === 'number' || t === 'symbol') return val as PropertyKey
return String(val) as PropertyKey
}
for (const it of list) {
const key = makeKey(it)
if (key === undefined && skipEmptyKey) continue
if (key === undefined) {
const k: PropertyKey = 'undefined'
if (keep === 'first') {
if (!(k in out)) Reflect.set(out, k, it)
} else {
Reflect.set(out, k, it)
}
continue
}
if (keep === 'first') {
if (!(key in out)) Reflect.set(out, key, it)
} else {
Reflect.set(out, key, it)
}
}
return out
}
export default keyBy