试图将 Typescript 函数限制为给定类型的字符串属性

Trying to limit Typescript function to string properties of a given type

基本上我正在尝试编写一个函数,该函数 returns 是给定类型的指定字符串 属性 的排序函数。

sortString<User>('name')

到目前为止我有以下内容:

type KeysAssignableToType<O, T> = {
    [K in keyof O]-?: O[K] extends T ? K : never;
}[keyof O];

export const sortString = <T>(field: KeysAssignableToType<T, string | undefined>) => (a: T, b: T): number => {
    if (a[field]) {
        return b[field] ? a[field].localeCompare(b[field]) : 1
    }
    return b[field] ? -1 : 0
}

但是存在以下问题。 TS给我一个TS2339: Property 'localeCompare' does not exist on type 'T[KeysAssignableToType ]'

有什么想法吗?

谢谢!

这是目前 TypeScript 的一个限制或缺失的功能。没有类型运算符的行为类似于 KeysAssignableToType<O, T>,其中编译器可以理解 O[KeysAssignableToType<O, T>] 可分配给 TOTgeneric. The sort of higher-order logic encoded there just isn't available to the compiler. There's an open feature request at microsoft/TypeScript#48992 时支持这样的类型运算符。直到并且除非它被实施,我们所拥有的只是解决方法。

一个解决方法是移动(或添加)约束,这样我们就可以说 K 被限制为 KeysAssignableToType<O, T>,而不是(或除了)说 KeysAssignableToType<O, T> =54=]object 类型 O 被限制为类似 Record<K, T> 的类型。即使 KT 是通用的,编译器也会让您使用键 K 索引到 Record<K, T>。例如:

type ObjWithKeys<O, T> =
    { [P in KeysAssignableToType<O, T>]?: T };

export const sortString = <T extends ObjWithKeys<T, string | undefined>>(
    field: KeysAssignableToType<T, string | undefined>) => (a: T, b: T): number => {
        const aField = a[field];
        const bField = b[field];
        if (aField) {
            return bField ? aField.localeCompare(bField) : 1
        }
        return bField ? -1 : 0
    }

之所以有效,是因为我们将 T 限制为等同于 Record<KeysAssignableToType<O, T>, T>(尽管我添加了 optional modifier 以便可选属性不会出现问题)。因此,当我们使用 field 索引到 ab 时,编译器知道结果将可分配给 string | undefined.

请注意,我在narrowing them with truthiness checks (side note: you really want undefined and "" to be equivalent in sort order? okay I guess). That's due to a bug/limitation mentioned at microsoft/TypeScript#10530; the compiler isn't able to narrow properties if the property index isn't a simple literal type之前将a[field]b[field]复制到它们自己的变量aFieldbField中,而field是当然不是简单的类型。通过使用单独的变量,我们可以完全忘记 属性 索引并专注于单个值。

无论如何,现在一切如愿以偿:

interface User {
    name: string,
    optionalProp?: string,
    age: number
}

sortString<User>('name') // ok
sortString<User>('optionalProp') // ok
sortString<User>('age') // error

Playground link to code