类型缩小仅允许特定类型的属性 'keyof T'

type narrowing for only allow attributes 'keyof T' of certain type

正在尝试使 keyof T 成为字符串 return 类型,并且在尝试将属性传递给 tags 时似乎可以正常工作。尽管将它用作索引时,它似乎并没有缩小它的范围,因为它将成为一个字符串。 错误:Type 'T[KeyOfType<T, string>]' is not assignable to type 'string'. 我做错了什么还是打字稿限制?

type KeyOfType<T, U> = { [P in keyof T]: T[P] extends U ? P : never }[keyof T];


interface TagProps<T> {
    tags: T[];
    tagLabelKey: KeyOfType<T, string>;
}

const tag = [{
    id: 1,
    name: 'raw',
}]

const tags = <T,>({tags, tagLabelKey}: TagProps<T>) => {
    const getLabel = (tag: T): string => {
        return tag[tagLabelKey];
    }
}

tags({ tags: tag, tagLabelKey: 'name' })

playground

这似乎确实是打字稿的限制。来自 this github issue 上的一位打字稿开发人员:

The problem here is that we don't have the higher-order reasoning to say "If a key came from an operation that filters out keys that aren't strings, then indexing by that key must have produced a string". The FilterProperties type is effectively logically opaque from TS's point of view.

他甚至提出了另一种实现相同语义的方法。转换为您的情况(并假设您要将 getLabel 映射到数组和 return 结果):

interface TagProps<P extends string> {
    tags: Record<P, string>[];
    tagLabelKey: P;
}

const tags = <P extends string,>({tags, tagLabelKey}: TagProps<P>) => {
    const getLabel = (tag: Record<P, string>): string => {
        return tag[tagLabelKey];
    }
    return tags.map(getLabel)
}

const tag = [{
    id: 1,
    name: 'raw',
}]

tags({ tags: tag, tagLabelKey: 'name' }) // typechecks
tags({ tags: tag, tagLabelKey: 'id' }) // does not typecheck

高级区别在于不是在标签对象类型中通用并提取字符串键(通过KeyOfType),在键中是通用的,并使用它构建标签对象的相关部分,同时只允许将字符串分配给它(通过Record<P, string>)。

或者使用更少的显式类型和更多的类型推断:

interface TagProps<P extends string> {
    tags: Record<P, string>[];
    tagLabelKey: P;
}

const tags = <P extends string,>({tags, tagLabelKey}: TagProps<P>): string[] => {
    return tags.map((tag) => tag[tagLabelKey])
}