首页 > 解决方案 > 获取只读数组元素成员的 TypeScript 类型

问题描述

我有一个readonly Pokemon[]名为PokemonData. 每个元素都有一个name成员。是否可以制作type一个串联的口袋妖怪名称列表?

interface Pokemon {
    readonly name: string;
    // ...
}

const PokemonData: readonly Pokemon[] = [{
    name: 'Pikachu',
    // ...
}, {
    name: 'Bulbasaur',
    // ...
}] as const;

type PokemonName = // TODO: should be 'Pikachu' | 'Bulbasaur'

如果我有一个只有名称的数组,我可以轻松地做到这一点:

const pokemonNames = [ 'Pikachu', 'Bulbasaur' ] as const;
type ArrayType<T> = T extends readonly (infer U)[] ? U : never;
type PokemonName = ArrayType<typeof pokemonNames>;

我怀疑,如果这完全可行,我需要与ArrayType<T>上面的对象属性等效的对象和/或接受 aPokemon并返回其名称的函数。

// using a type approach (doesn't work)
type GetPokemonName<T extends Pokemon> = T['name'];
type GetPokemonNames<T> = T extends Pokemon[] ? GetPokemonName<T[/* something here */]> : never;

// using a function approach (may work, but I don't know how to convert it to a type)
function getPokemonName(pkmn: Pokemon) {
    return pkmn.name;
}

我还认为可能ReadonlyArray<T>.map()有用,但它只返回一个字符串类型。

const pokemonNames = PokemonData.map(p => p.name); // using 'as const' throws a compile-time error
type PokemonName = ArrayType<typeof pokemonNames>;

标签: typescript

解决方案


Typescript 能够将数组中的名称视为字符串文字类型的唯一方法是删除: readonly Pokemon[]类型注释;否则任何typeof人都将查看Pokemon类型的声明,而不是从数组的实际内容推断出的更具体的类型。

如果删除该注释,则名称可以映射到联合类型:

const PokemonData = [{
    name: 'Pikachu'
}, {
    name: 'Bulbasaur'
}] as const;

type PokemonName = (typeof PokemonData)[number]['name'];

[number]数组类型映射到其组件类型,然后映射到与属性['name']关联的字符串文字类型的联合。name

这不是一个理想的解决方案,因为删除Pokemon[]类型注释意味着 Typescript 不会检查其内容是否为有效Pokemon对象。如果需要,可以编写一个辅助函数:

function pokemon<K extends string>(obj: Pokemon & { name: K }): Pokemon & { name: K } {
    return obj;
}

const PokemonData = [
    pokemon({
        name: 'Pikachu',
    }),
    pokemon({
        name: 'Bulbasaur',
    }),
] as const;

游乐场链接


推荐阅读