TypeScript 实现双向映射

1,174 阅读1分钟

实现

类似ts的enum,双向映射,同时可用于for...of循环

2022-07-25 更新
根据类型体操4179-Flip,翻转对象有更简单直接的实现

/** 翻转对象键与值 */
export type Flip<Obj extends Record<string, string | number>> = {
  [Key in keyof Obj as Obj[Key]]: Key
}

/** 可用于 `${}` 的类型 */
export type Stringable = string | number | bigint | boolean | null | undefined

/** 更宽松的键值 */
export type FlipLoose<Obj extends Record<string | number, Stringable>> = {
  [Key in keyof Obj as Obj[Key] extends keyof any ? Obj[Key] : Obj[Key] extends Stringable ? `${Obj[Key]}` : never]: Key extends Stringable ? `${Key}` : never
}

// 以下注释部分被 Flip 取代
/** 获取 value 类型 */
// export type ValueOf<O extends Record<keyof any, any>> = O[keyof O]
/** 工具类型 根据值获取键 */
// type KeysMatching<Obj, Val> = { [Key in keyof Obj]-?: Obj[Key] extends Val ? Key : never }[keyof Obj]
/** 工具类型 把值转换成键类型 */
// type SetValuesTokeyType<T extends Record<keyof any, keyof any | boolean | null | undefined>> = {
//   [K in keyof T]: T[K] extends keyof any
//     ? T[K]
//     : T[K] extends bigint | boolean | undefined | null
//       ? `${T[K]}`
//       : never
// }
/** 工具类型 反转对象值与键 */
// export type Reverse<Obj extends Record<keyof Obj, Obj[keyof Obj]>> = { [Val in ValueOf<Obj>]: KeysMatching<Obj, Val> }
// export type ReverseLoose<T extends Record<keyof any, keyof any | boolean | null | undefined>> = Reverse<SetValuesTokeyType<T>>

/** 生成双向映射  */
export function useEnum<T extends Record<string | number, string | number | boolean | null | undefined>>(obj: T)
  : Readonly<
    & T
    & FlipLoose<T>
    & { [Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]> }
  > {
  const newObj = Object.create(null)

  // 创建自定义迭代器 可以 for(const [key, val] of myEnum) {  }
  Object.defineProperty(newObj, Symbol.iterator, {
    enumerable: false,
    value: () => Object.entries(obj)[Symbol.iterator]()
  })

  for (const key in obj) {
    newObj[String(newObj[key] = obj[key])] = key
  }
  return Object.freeze(newObj)
}

使用

const myEnum = useEnum({
  '数学': 1,
  '语文': 2,
  '英语': 3,
  '化学': false,
  '物理': true,
  '历史': undefined,
  '地理': null
} as const)

console.log(myEnum[1])
for (const [key, val] of myEnum) {
  console.log(key, val)
}

效果如图 Snipaste_2022-04-11_16-49-27.png Snipaste_2022-04-11_16-50-57.png

在vue组件中使用

<template>
  <!-- 选项 -->
  <el-select clearable v-model="optionValue">
    <el-option
      v-for="[label, value] of myEnum"
      :key="label"
      :value="(value as any)"
      :label="label"
    />
  </el-select>

  <!-- 使用 -->
  <div>{{ myEnum[optionValue] }}</div>
</template>

<script lang="ts" setup>
const optionValue = ref('')
const myEnum = useEnum({
  '数学': 1,
  '语文': 2,
  '英语': 3,
  '化学': false,
  '物理': true,
  '历史': undefined,
  '地理': null
} as const)
</script>