【问题标题】:typescript how to derive type from dynamic array?打字稿如何从动态数组派生类型?
【发布时间】:2021-09-16 18:38:39
【问题描述】:

这里有 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[] 类型

我怎样才能做到这一点?

谢谢。

【问题讨论】:

    标签: arrays reactjs typescript dynamic


    【解决方案1】:

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

    我认为最好是做typeguard和utility function:

    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

    【讨论】:

    • 这太棒了 - 我仍在努力了解发生了什么,但非常感谢您的解决方案!
    • 稍等,我会写详细解释
    猜你喜欢
    • 2017-12-28
    • 2020-06-15
    • 2022-12-10
    • 2018-11-19
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    相关资源
    最近更新 更多