封装一个字典函数
完整代码: https://codesandbox.io/s/awesome-margulis-q2xibv?file=/src/index.ts
潜在的问题
在我们平常写代码的时候,可能需要对一个相同的数据进行各种转换,以便满足业务的需求,例如下方社交账号的例子:
const social = {
bilibili: '291833916',
netease: '345345345',
weibo: '436453345',
}
if (social[type]) {
// do something
}
这样可能可以满足当时的需求,但如果哪天业务变更,要求加入社交账号的 icon,你可能会改成这样:
export const social = [
{
name: 'bilibili',
account: '291833916',
icon: 'bilibili.svg',
},
{
name: 'netease',
account: '345345345',
icon: 'netease.svg',
},
{
name: 'weibo',
account: '436453345',
icon: 'weibo.svg',
},
// ...
];
但这样改的话,你之前的代码可能会报错。当然你也可能会重新创建一个对象来避免错误,但这样代码就越来越臃肿了。
又如果你以后需要用到所有社交账号的名称或者账号(下方的格式),你可能又需要通过 map 来获取到。
['bilibili', 'netease', 'weibo']
['291833916', '345345345', '436453345']
这样的代码又臃肿又混乱,而且还容易出错。因此我们完全可以封装一个工具函数,将一份定义转换成多种格式,从而实现如下效果。
{
SOCIAL_TYPE_KEYS: [ 'bilibili', 'netease', 'weibo' ],
SOCIAL_TYPE_VALUES: [ '291833916', '345345345', '436453345' ],
SOCIAL_TYPE_KV: { bilibili: '291833916', netease: '345345345', weibo: '436453345' },
SOCIAL_TYPE_VK: {
'291833916': 'bilibili',
'345345345': 'netease',
'436453345': 'weibo'
},
SOCIAL_TYPE_MAP_BY_KEY: {
bilibili: { key: 'bilibili', value: '291833916', icon: 'bilibili.svg' },
netease: { key: 'netease', value: '345345345', icon: 'netease.svg' },
weibo: { key: 'weibo', value: '436453345', icon: 'weibo.svg' }
},
SOCIAL_TYPE_MAP_BY_VALUE: {
'291833916': { key: 'bilibili', value: '291833916', icon: 'bilibili.svg' },
'345345345': { key: 'netease', value: '345345345', icon: 'netease.svg' },
'436453345': { key: 'weibo', value: '436453345', icon: 'weibo.svg' }
},
SOCIAL_TYPE_KEY_MAP: {
bilibili: { key: 'bilibili', value: '291833916', icon: 'bilibili.svg' },
netease: { key: 'netease', value: '345345345', icon: 'netease.svg' },
weibo: { key: 'weibo', value: '436453345', icon: 'weibo.svg' }
},
SOCIAL_TYPE_MAP: { bilibili: '291833916', netease: '345345345', weibo: '436453345' },
SOCIAL_TYPE_LIST: [
{ key: 'bilibili', value: '291833916', icon: 'bilibili.svg' },
{ key: 'netease', value: '345345345', icon: 'netease.svg' },
{ key: 'weibo', value: '436453345', icon: 'weibo.svg' }
]
}
函数编写
我们可以编写如下函数:
export const social = [
{
key: 'bilibili',
value: '291833916',
icon: 'bilibili.svg',
},
{
key: 'netease',
value: '345345345',
icon: 'netease.svg',
},
{
key: 'weibo',
value: '436453345',
icon: 'weibo.svg',
},
// ...
]
function defineConstants(list) {
return {
SOCIAL_KEYS: list.map((item) => item.key),
SOCIAL_KV: list.reduce(
(map, item) => ({
...map,
[item.key]: item.value,
}),
{},
),
....
}
}
const data = defineConstants(social)
console.log(data);
//{
// SOCIAL_KEYS: [ 'bilibili', 'netease', 'weibo' ],
// SOCIAL_KV: { bilibili: '291833916', netease: '345345345', weibo: '436453345' }
//}
大致思路就是这样,但为了代码的通用性我们应当再传递一个参数,来当我们的前缀。
function defineConstants(list, namespace) {
const prefix = namespace ? `${namespace}_` : ''
return {
[`${prefix}KEYS`]: list.map((item) => item.key),
[`${prefix}KV`]: list.reduce(
(map, item) => ({
...map,
[item.key]: item.value,
}),
{},
),
}
}
但这样的函数是没有类型提示的,因此我们需要使用 TypeScript 来进行类型定义:
interface IBaseDef {
key: PropertyKey;
value: string | number;
}
function defineConstants<T extends IBaseDef[], N extends string>(
defs: T,
namespace?: N,
) {
const prefix = namespace ? `${namespace}_` : '';
return {
[`${prefix}KEYS`]: defs.map((item) => item.key),
};
}
这样传入的参数便有了类型,我们还需要对返回值进行定义:
type ToProperty<Property extends string, N extends string = ""> = N extends ""
? Property
: `${N}_${Property}`;
export type MergeIntersection<A> = A extends infer T
? { [Key in keyof T]: T[Key] }
: never;
type ToKeyValue<T> = T extends readonly [infer A, ...infer B]
? B["length"] extends 0
? ToSingleKeyValue<A>
: MergeIntersection<ToSingleKeyValue<A> & ToKeyValue<B>>
: [];
type ToKeys<T> = T extends readonly [infer A, ...infer B]
? A extends {
readonly key: infer K;
}
? B["length"] extends 0
? [K]
: [K, ...ToKeys<B>]
: never
: [];
type ToSingleKeyValue<T> = T extends {
readonly key: infer K;
readonly value: infer V;
}
? K extends PropertyKey
? {
readonly [Key in K]: V;
}
: never
: never;
function defineConstants<T extends readonly IBaseDef[], N extends string = "">(
defs: T,
namespace?: N
) {
const prefix = namespace ? `${namespace}_` : "";
return {
[`${prefix}KEYS`]: defs.map((item) => item.key),
[`${prefix}KV`]: defs.reduce(
(map, item) => ({
...map,
[item.key]: item.value,
}),
{}
),
} as MergeIntersection<{
[Key in ToProperty<"KV", N>]: ToKeyValue<T>;
}> & {
[Key in ToProperty<"KEYS", N>]: ToKeys<T>;
};
}
TypeScript 涉及的知识点过多,就不再叙述了,最终代码可以看开头 codesandbox
。