打字稿如何从动态数组派生类型?

时间:2021-07-06 06:05:09

标签: arrays reactjs typescript dynamic

这里有 TS 的菜鸟。我正在 React 中创建一个自定义图标组件,我只想使用圆形材质 UI 图标。

我像这样定义类型:

import * as icons from '@material-ui/icons/index'
export type MaterialUiIcon = keyof typeof icons

这有效,但只能让我检查所有材料 UI 图标的类型。

相反,这就是我想要做的:

const roundedIconsNames = (Object.keys(icons).filter((icon) => icon.includes('Rounded')))
export type RoundedMaterialUiIcon = typeof roundedIconsNames[number]

然而 RoundedMaterialUiIcon 最终成为 string[] 类型

我怎样才能做到这一点?

谢谢。

1 个答案:

答案 0 :(得分:1)

您收到 string[] 是因为 Object.keys 总是按设计返回 string[]

我认为最好制作 typeguard 和实用函数:

import * as icons from '@material-ui/icons/index'
type Icons = typeof icons

export type MaterialUiIcon = keyof Icons

type Prefix<T extends string> = `${string}${T}` | `${string}${T}${string}` | `${T}${string}`

// self explanatory, we have 3 variant of word
type Test1 = Prefix<'Rounded'> // `${string}Rounded` | `${string}Rounded${string}` | `Rounded${string}`


type GetByPrefix<T, P> = T extends P ? T : never;

/**
 * This will return only HelloRounded, because union 'HelloRounded' | 'Batman'
 * extends `${string}Rounded` | `${string}Rounded${string}` | `Rounded${string}`
 * 
 * WHy extends?
 * Because Rounded is at the end
 */
type Test2 = GetByPrefix<'HelloRounded' | 'Batman', Prefix<'Rounded'>> // 'HelloRounded'

/**
 * This will return "RoundedWorld" because Rounded is at the beginning
 */
type Test3 = GetByPrefix<'RoundedWorld' | 'Batman', Prefix<'Rounded'>> // "RoundedWorld"

/**
 * This is a special syntax for user defined typeguards
 * It may help you to narrow the type
 * https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
 */
const typeguard = <T extends string,>(tag: string) => (icon: MaterialUiIcon): icon is GetByPrefix<MaterialUiIcon, Prefix<T>> => icon.includes(tag)

const getRounded = <T extends string>(icons: Icons, includes: T) =>
/**
 * Object.keys will always return string[], this is by design
 * So you need to use type assertion here
 * 
 * Array.prototype.filter accept curried typeguard
 * It is a good practive yo use user defined typeguards a a predicate
 * callback in array methods
 */
    (Object.keys(icons) as Array<MaterialUiIcon>).filter(typeguard<T>(includes))

const result = getRounded(icons, 'Rounded')


请记住,由于 Icons 有 5K 联合类型,因此 TS 遍历所有联合并非易事。

Playground

相关问题